本文已参与「新人创作礼」活动,一起开启掘金创作之路。
单例模式简介
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式使用getInstance() 方法获取唯一的实例,但是调用时中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
单例模式实现思路
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
根据上面的要求,依次实现即可:
package singleton;
public class Single {
// 自己创建、唯一实例
private static final Single instance = new Single();
// 外部无法调用构造器
private Single(){}
// 对外提供实例
public static Single getInstance(){
return instance;
}
}
单例模式的设计
单例模式的设计需要考虑以下因素
- 是否延迟加载:解决资源浪费问题
- 是否多线程安全
- 实现难易程度
单例模式的八种实现方法
饿汉式(可用)
饿汉式解决了多线程安全问题
①静态常量式
上述写法称为使用静态常量的饿汉式
package singleton.hungryMan;
public class StaticConstanceStyle {
private static final StaticConstanceStyle INSTANCE = new StaticConstanceStyle();
private StaticConstanceStyle(){}
public static StaticConstanceStyle getInstance(){
return INSTANCE;
}
}
②静态代码块式
实例化操作还可以放在静态代码块里面
package singleton.hungryMan;
public class StaticBlockStyle {
private static final StaticBlockStyle instance;
static {
instance = new StaticBlockStyle();
}
private StaticBlockStyle(){}
public static StaticBlockStyle getInstance(){
return INSTANCE;
}
}
以上两种写法效果一致
优点:多线程安全,没有加锁、执行效率高、实现简单、常用 缺点:未实现延迟加载,如果实例从未调用会造成浪费
懒汉式(不可用/不推荐)
懒汉式解决延迟加载问题
③线程不安全式
在饿汉式的基础上修改:增加判空达到延迟加载
package singleton.lazyMan;
public class ThreadSafety {
private static ThreadSafety instance;
private ThreadSafety(){}
public static ThreadSafety getInstance(){
if (instance == null){
instance = new ThreadSafety();
}
return instance;
}
}
但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
优点:实现延迟加载、实现简单 缺点:最大的缺点就是多线程不安全
线程安全式
只要解决上面的线程不安全即可,主要是对getInstance方法做线程同步
④同步方法式
package singleton.lazyMan.ThreadUnsafety;
public class MethodSync {
private static MethodSync instance;
private MethodSync(){}
public static synchronized MethodSync getInstance(){
if (instance==null){
instance = new MethodSync();
}
return instance;
}
}
缺点:效率非常低、每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
⑤同步代码块式
package singleton.lazyMan.ThreadUnsafety;
public class BlockSync {
private static BlockSync instance;
private BlockSync(){}
public static BlockSync getInstance(){
if (instance==null){
synchronized (BlockSync.class){
instance = new BlockSync();
}
}
return instance;
}
}
缺点:线程不同步,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
⑥双重校验锁(Double-checked Locking, DCL)(推荐)
双检锁改进自懒汉式的同步代码块写法,进行两次判空,只进行一次实例化,下次直接return
JDK限定≥1.5
package singleton;
public class DCL {
// 必须使用volatile
private static volatile DCL instance;
private DCL(){}
public static DCL getInstance(){
if (instance==null){
synchronized (DCL.class){
if (instance==null){
instance = new DCL();
}
}
}
return instance;
}
}
优点:安全高性能
Volatile关键字的作用: 禁止进行指令的重排序
⑦静态内部类/登记式(推荐)
写法类似饿汉式,都是采用了类装载的机制来保证初始化实例时只有一个线程。
package singleton;
public class StaticInternalClass {
private StaticInternalClass(){}
private static class StaticInternalInstanceHolder{
private static final StaticInternalClass INSTANCE = new StaticInternalClass();
}
public static final StaticInternalClass getInstance(){
return StaticInternalInstanceHolder.INSTANCE;
}
}
效果和双检锁一样,利用类加载机制保证初始化instance只有一个线程,这种方法即使StaticInternalClass被装载、instance也不一定被初始化(因为在静态内部类之中),只有显式调用getInstance才会实例化instance
优点:线程安全、延迟加载、效率高
⑧枚举
JDK限定≥1.5
package singleton;
public enum Enum {
INSTANCE;
public void hello(){
System.out.println("Hello!");
}
}
直接调用方法即可:
package singleton;
public class Main {
public static void main(String[] args) {
singleton.Enum.INSTANCE.hello();
}
}
优点: **Effective Java 作者 Josh Bloch 提倡!**避免了多线程同步问题,能够防止反序列化重新创建新的对象。
使用总结
- 建议用饿汉式
- 有延迟加载需求才用登记式
- 涉及反序列化用枚举
- 其他特殊需求用双检锁
- 不建议用懒汉式