理解单例模式

Posted by AceKei on June 10, 2019

是什么

Ensure a class only has one instance, and provide a global point of access to it.

确保一个类只有一个实例,并且提供一个全局的方法来访问这个类。

单例模式的实现

  • 饿汉式
  • 懒汉式
  • 静态内部类
  • 枚举

优缺点

优点:

全局提供一个类的实例,防止一个类被重复实例。

缺点

类内的全局变量的使用涉及到非线程安全。

饿汉式

1
2
3
4
5
6
7
8
public class Singleton {  
     private static Singleton instance = new Singleton();  
     private Singleton (){
     }
     public static Singleton getInstance() {  
        return instance;  
     }  
 } 

在类的加载的时候就已经进行了初始化,所以是线程安全的,但是类的加载会影响程序的启动时间。

懒汉式

非线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
    //私有构造函数
    private Singleton() {}  
    //单例对象
    private static Singleton instance = null;  
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

当多个进程同时来调用该方法时:假如A,B线程同时来到了 if 判断语句这里,同时判断为 true ,那么就获得了2个实例了。

当想要获取对应的单例时,再实例化该类,从而不再影响程序的启动。

线程安全

双重检验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {
    //私有构造函数
    private Singleton() {} 
    //单例对象,使用 volatile 关键字禁止指令重排序
    private volatile static Singleton instance = null; 
    //静态工厂方法
    public static Singleton getInstance() {
        //双重检测机制
        if (instance == null) {      
            //同步锁
             synchronized (Singleton.class){  
                //双重检测机制
                if (instance == null) {     
                    instance = new Singleton();
                }
            }
         }
        return instance;
    }
}

注意在声明 单例 的时候,使用 volatile 关键字禁止指令重排序。

静态内部类

1
2
3
4
5
6
7
8
9
10
public class Singleton { 
    private Singleton(){
    }
    public static Singleton getInstance(){  
        return SingletonHolder.instance;  
    }  
    private static class SingletonHolder {  
        private static final Singleton instance = new Singleton();  
    }  
} 

第一次加载 Singleton 类时并不会初始化 instance ,只有第一次调用 getInstance() 方法时虚拟机加载 SingletonHolder 并初始化instance ,这样不仅能确保线程安全也能保证 Singleton 类的唯一性。

枚举

1
2
3
4
5
public enum Singleton {  
     INSTANCE;  
     public void doSomeThing() {  
     }  
 } 

枚举声明的单例是线程安全的,在程序启动的时候就会加载该类。 枚举 声明的类不会因为反序列化或者反射就能获得第二个该类的实例,但是其他的几种模式会。

反射打破单例

1
2
3
4
5
6
7
8
9
//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1 == singleton2);

注意

  1. 使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。
  2. 对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现 readResolve 方法。
单例模式 是否线程安全 是否懒加载 是否防止反射,反序列化构建
饿汉式
双重检验锁
静态内部类
枚举

java中的应用

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

参考

  • https://github.com/iluwatar/java-design-patterns/tree/master/singleton
  • https://zhuanlan.zhihu.com/p/33102022