一、【饿汉模式】- 多线程安全单例模式实例一(不使用同步锁)
缺点:对象在没有使用之前就已经初始化了。这就可能带来潜在的性能问题:如果这个对象很大呢?没有使用这个对象之前,就把它加载到了内存中去是一种巨大的浪费。另外,当系统中这样的类较多时,会使得启动速度变慢 。
public class EagerSingleton {
private static final EagerSingleton singleton = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getSingleton() {
return singleton;
}
}
二、【懒汉模式】多线程安全单例模式实例二(使用同步方法)
缺点:一次锁住了整个方法,粒度有些大。改进--只在实例化语句加锁
public class LazySingleton {
private static LazySingleton singleton = null;
private LazySingleton() {
}
public static synchronized LazySingleton getSingleton() {
if (singleton == null)
singleton = new LazySingleton();
return singleton;
}
}
三、【懒汉模式】多线程安全单例模式实例三(使用同步方法-双重检查锁Double-Checked Lock)
双重检查的原因:可能会有多个线程同时同步块外的if判断语句,不进行双重检查就会导致创建多个实例。
同时因为singleton = new LazySingleton()不是原子操作,而是分为3个步骤进行:
①给singleton分配内存 ②在空间内创建对象即初始化 ③将singleton变量指向分配的内存空间
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的②和③的顺序是不能保证的,最终的执行顺序可能是 ①②③也可能是 ①③②。如果是后者,则在③执行完毕、②未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后就会报错。
解决办法就是要将 singleton变量声明成 volatile 就可以了,因为volatile有禁止指令重排的特性。
Synchronized可依保证程序的有序性(即程序执行的顺序按照代码的先后顺序执行),但并无法禁止指令重排和处理器优化的。
[但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。]
public class LazySingleton {
private volatile static LazySingleton singleton = null;
private LazySingleton() {
}
public static LazySingleton getSingleton() {
if (singleton == null) {
synchronized (LazySingleton.class) {
if (singleton == null)
singleton = new LazySingleton();
}
}
return singleton;
}
}
但是,实际还是可以通过反射方式创建多个实例:
public class SingletonTest {
public static void main(String[] args) throws Exception{
Class clz = Class.forName("LazySingleton");
// "true" indicates that the reflected object should suppress(废弃) Java language access checking when it is used
Constructor constructor = clz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj1 = constructor.newInstance();
Object obj2 = constructor.newInstance();
}
}
四、【懒汉模式】多线程安全单例模式实例四(使用静态内部类《Effective Java》上所推荐的)
外部类被加载时内部类并不需要立即加载内部类,内部类不被加载则不需要进行类初始化,因此单例对象在外部类被加载了以后不占用内存。
public class Singleton {
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
五、单例模式实例五(实际没有用过,待验证)
我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。
public enum EasySingleton{
INSTANCE;
}