一、线程不安全
public class SampleOne {
private static SampleOne instance;
private SampleOne() {}
public static SampleOne getInstance() {
if (instance == null) {
instance = new SampleOne();
}
return instance;
}
}
二、加同步锁,线程安全但效率低
public class SampleTwo {
private static SampleTwo instance;
private SampleTwo() {}
public synchronized static SampleTwo getInstance() {
if (instance == null) {
instance = new SampleTwo();
}
return instance;
}
}
三、双检锁(DCL)
public class SampleThree {
private static volatile SampleThree instance;// 语句1
private SampleThree() {}
public static SampleThree getInstance() {
if (instance == null) {// 语句2
synchronized(SampleThree.class) {
if (instance == null) {
instance = new SampleThree();// 语句3
}
}
}
return instance;
}
}
TIPS:
语句1处,加上volatile才是正确的双检锁。
对于
instance = new SampleThree();
这个语句来说,实际包含三条字节码指令:
- 1.在堆内为新对象开辟一段内存空间;
- 2.初始化新对象中的属性;
- 3.将instance指向新开辟的内存。
其中2、3步因为没有严格的“happen-before”原则,存在指令重排的可能,3和2的顺序将可能被调换。那么,这就可能导致:对象内部属性还未初始化,但instance却已经非null,从而得到了一个未正常初始化的、非线程安全的实例。
四、基于类加载机制
JAVA虚拟机有且仅有在以下5种场景下会对类进行初始化:
- 遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
- 当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
- 当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
而JVM在初始化类的时候,会自己控制类在多线程环境下被正确初始化,包括运用加锁、同步方法,当多个线程同时初始化一个类时,只有一个线程能去执行类型初始化的方法,其他的线程将被阻塞。
包括将单例对象置为静态属性、内部类单例、枚举类型单例这些实现单例模式的方法,其实都属于这一类。
将单例对象置为静态属性
public class SampleFour {
private static final SampleFour instance = new SampleFour();
private SampleFour() {}
public static SampleFour getInstance() {
return instance;
}
}
内部类单例
public class SampleFour {
private SampleFour() {}
private static class SingletonHolder {
private static final SampleFour instance = new SampleFour();
}
public static SampleFour getInstance() {
return SingletonHolder.instance;
}
}
枚举类型单例
public enum SampleFour {
instance;
public void method() {
// TODO:
}
}
五、CAS
public class SampleFive {
private static final AtomicReference<SampleFive> instance = new AtomicReference<>();
private SampleFive() {}
public static SampleFive getInstance() {
for (;;) {
SampleFive sampleFive = instance.get();
if (sampleFive != null) {
return sampleFive;
} else {
sampleFive = new SampleFive();
if (instance.compareAndSet(null, sampleFive)) {
return sampleFive;
}
}
}
}
}
参考资料:
《深入理解JAVA虚拟机》
《Java虚拟机规范》