单件模式 Singleton Pattern
类只存在一个实例,即只可以创建一个对象。有一些类如果创造出多个对象就会导致许多问题的产生,如程序的行为异常、资源使用过量,或者是不一致的结果。单件模式常常被用来管理共享的资源,例如数据库连接或者线程池。
简要定义:单件模式确保一个类只有一个实例,并提供一个全局访问点
单件模式的实现
单件模式的实现是通过private构造函数,类中含有一个静态方法getInstance(),调用这个方法可以创建并返回唯一的对象,也可能返回的是已经创建好的对象。
分析:单件模式,首先要限制从外部创建对象,所以将构造方法声明为私有private,这样就只能在类内部调用构造函数。如何调用这个构造函数?要使用这个类,而又没有这个类的对象,所以需要一个静态的方法,这个方法调用私有的构造函数,并且返回唯一的单件对象。
剖析经典单件模式的实现
下面是经典单件模式的实现:
public class Singleton {
private static Singleton singleton = null;
private Singleton() { }
public static Singleton getInstance() {
if(singleton== null) {
singleton=newSingleton();
}
return singleton;
}
}
这种实现方式存在问题,在多线程的情况下,可能会创建多个对象。
我们可以通过增加synchionized关键字到getInstance方法中,迫使每个线程在进入这个方法之前,要先等候别的进程离开该方法。
public class Singleton {
private static Singleton singleton = null;
private Singleton() { }
public static synchionized Singleton getInstance() {
if(singleton== null) {
singleton=newSingleton();
}
return singleton;
}
这样的同步可能会造成性能问题,改善途径有两种:
1.使用“急切”创建实例,而不用延迟实例化的做法:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
利用这个做法,我们依赖JVM在加载在加载这个类时马上创建唯一的单件实例.JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例
如果我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。可以用如下版本实现:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = newSingleton();
}
privateSingleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
上面这种方式,仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
2.使用"双重检查加锁“,在getInstance中减少使用同步:
首先检查是否实例已经创建了,如果未创建,才进行同步,这样一来,只有第一次会同步。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}<span style="font-size:18px;"><strong>
volatile</strong>关键字的作用就是确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量</span>
需要把singleton声明成成 volatile 这是因为:singleton = new Singleton()
这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给 singleton 分配内存
- 调用 Singleton 的构造函数来初始化成员变量,形成实例
- 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。