普通线程安全的单例模式
描述
即在一个JVM中,一个对象只有一个实例存在
问题
指令重排
在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();
语句是分两步执行的,但是JVM并不保证这两个操作的先后顺序;
理想顺序是:
a.分配对象的内存空间;
b.初始化对象;
c.设置instance指向刚分配的内存地址;
但经过JVM编译和CPU优化之后有可能是a>c>b;
即指令重排之后,当A线程执行同步代码,并执行到c后,此时B线程执行同步代码的第一次null检测,此时instance!=null
,从而返回了一个没有初始化完成的instance对象;
线程安全优化的单例模式
描述
-
利用类加载的过程实现懒加载;
单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载时,这个类的加载过程是线程互斥的;
当第一次调用getInstance的时候,JVM能够保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样就不用担心1.1问题;同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题;
-
使用volatile修饰符
该修饰符阻止了遍历访问前后的指令重排,保证了指令执行顺序;
java volatile关键字
适用
解决指令重排的问题
问题
- 思想1不一定完美,若在构造函数中抛出异常,实例将永远得不到创建,会出错;
- 思想2无法防止利用反射来重复构建对象;
示例
- 思想1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Singleton {
private Singleton() {} private static class SingletonFactory{ private static final Singleton instance = new Singleton(); } public static Singleton getInstance(){ return SingletonFactory.instance; } public Object readResolve(){ return getInstance(); } }
|
- 思想2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized(this) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
线程安全高级单例模式
描述
因为只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字
适用
解决内部类或者volatile
关键字遇到的问题1
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Singleton { private static Singleton instance = null; private Singleton() {} private static synchronized void syncInit() { if (instance == null) { instance = new Singleton(); } } public static Singleton getInstance(){ if (instance == null) { syncInit(); } return instance; } public Object readResolve(){ return instance; } }
|
反射问题
利用反射打破单例
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.equals(singleton2));
|
解决,利用枚举
优点:利用枚举实现的单例模式不仅能够防止反射构造对象,还能保证线程安全;
缺点:它并非使用懒加载,其实例对象是在枚举类被加载的时候进行初始化的;
1 2 3
| public enum SingletonEnum { INSTANCE; }
|