单例模式

Posted by kyle on April 28, 2019

一、线程不安全

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虚拟机规范》