首页 > 代码库 > 单例模式笔记

单例模式笔记

定义

单例模式是限制类的实例只有一个的设计模式。

代码

单线程下的单例模式代码

public class SimpleSingleton {
    private static SimpleSingleton simpleSingleton;
?
    private SimpleSingleton() {}
?
    public static SimpleSingleton getInstance() {
        // 如果 simpleSingleton 未进行实例化,则创建实例,之后都使用此实例
        if (simpleSingleton == null) {
            simpleSingleton = new SimpleSingleton();
        }
        return simpleSingleton;
    }
}

 

但是如果在多线程的情况下,可能出现在第一个线程 A 线程判断simpleSingleton为空时,进入simpleSingleton = new Simpleton();部分进行代码实例化,但在 A 线程还未实例化结束时,另一个线程 B 线程进行判断simpleSingleton是否为空,此时由于 A 线程还没有实例化赋值给simpleSingleton,所以其仍未null,这是 B 线程也会进入执行simpleSingleton = new Simpleton();,这样就会导致出现不同的实例,所以这种方法只能用在单线程的情况下。

多线程下的单例模式代码

多线程下最简单的方式是对getInstance()方法整个加上synchronized修饰符,保证每次只有一个线程能进入此方法。但是这样做会很影响性能,我们应该尽量减小锁作用的范围,所以最好采用如下的双重加锁方式:

public class SynchronizedSingleton {
?
    private static SynchronizedSingleton singleton;
?
    private SynchronizedSingleton() {}
?
    public static SynchronizedSingleton getInstance() {
        if (singleton == null) {
            synchronized (SynchronizedSingleton.class) {
                if (singleton == null) {
                    singleton = new SynchronizedSingleton();
                }
            }
        }
        return singleton;
    }
}

 

之所以进行第二次singleton == null判断是因为在线程 A 在第一次进行判断为null后获得锁进行实例化,在实例化未完成时,B 线程判断仍为null,这是由于获得不了锁,所以等待,在 A 线程创建实例后释放了锁,这时 B 线程获得锁并执行,如果此时不进行第二次的singleton == null判断,则 B 线程也会创建一个新的实例,导致单例模式出现问题;而如果进行第二次判断,则会得知singleton已经被实例化,就不会再创建新的实例。

 

目前一切看起来都很美好,但是仍然有一个问题需要解决。

创建对象实例可以分为三个步骤:

  1. 分配内存

  2. 调用构造函数

  3. 将对象指向分配的内存地址

之前的代码如果依此顺序执行,则不会有问题。但是为了提高性能,编译器和处理器常常会对指令进行重排序,这时如果步骤 2 和步骤 3 的顺序颠倒了,先将对象指向分配的内存地址,后执行构造函数那么就会出现问题。当 A 线程将对象指向分配的内存地址,但还未执行构造函数的时候, B 线程进入,判断对象不为空,则将对象引用返回,这时如果使用此引用,则会出现问题。

目前有三个解决方法:

1. 给静态实例属性加上 volatile关键字(需要 JDK 1.5 及之后版本)
private static volatile SynchronizedSingleton singleton;

 

volatile 关键字可以保证对 volatile 变量的操作不会进行重排序。

2. 使用单个元素的枚举类型(需要 JDK 1.5 及之后版本)
public enum Singleton {
    INSTANCE;
}

 

3. 使用子类,由 JVM 保证单例
public class InnerClassSingleton {
    public static Singleton getInstance() {
        return Singleton.singleton;
    }
?
    private static class Singleton {
        static Singleton singleton = new Singleton();
    }
}

 

类的静态属性只会在第一次加载的时候初始化一次,同时 JVM 保证在初始化的过程中(未完成时)无法被使用。

 

 

单例模式笔记