Important:推荐使用枚举方式实现单例模式!
Outlines
经典的单例模式
处理多线程:同步加锁创建
处理多线程:同步加锁创建(优化版:双重检查加锁double-checked locking)
静态初始化:非延迟创建
防止反射
枚举方式(实现单例模式推荐使用的方式)
e.g. 创建单例线程池
有些时候我们只想要实例化一个对象,比如线程池对象、缓存对象、日志对象以及充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象也只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如程序的行为异常、资源使用过量、结果不一致等等。这个时候就需要我们使用Singleton Pattern单例模式了。
单例模式:确保一个类只有一个实例,并提供一个全局访问点。
经典的单例模式
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) { //1
uniqueInstance = new Singleton(); //2
}
return uniqueInstance;
}
}
这个方式的一大弊端在于无法胜任多线程应用。比如现在有两个线程X和Y,其中X运行到了1的位置,Y运行到了2的位置,此时Y线程已经创建了一个实例,而下一个时刻,线程X又会执行一遍uniqueInstance = new Singleton(),相当于又创建了一个实例。
处理多线程:同步加锁创建
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() { //1
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
我们在1处添加了synchronized关键字对整个函数进行加锁操作,保证同一时刻只有一个线程能够访问该函数,避免了线程不安全的问题。但是该方式的一大弊端在于会降低近100倍的效率。更糟糕的是,只有第一次执行时,才真正需要同步。即一旦设置好uniqueInstance变量后,就不再需要同步这个方法了,之后每次调用这个方法,同步都是一种累赘。该方式还可以进行优化,即只同步代码块。
处理多线程:同步加锁创建(优化版:双重检查加锁double-checked locking)
public class Singleton {
private static volatile Singleton uniqueInstance; //1 这里添加了volatile关键字
private Singleton() {}
public static Singleton getInstance() { //2 这里取消了synchronized关键字
if(uniqueInstance == null) { //3 如果已经被实例化了,则直接返回
synchronized (Singleton.class) { //4 同步代码块
if(uniqueInstance == null) { //5 第二重检查
uniqueInstance = new Singleton(); //6
} //7
} //8
} //9
return uniqueInstance;
}
}
这里的5为什么要进行第二重检查呢?比如现在有两个线程X和Y,X运行到了3的位置,Y是第一个进入该函数的线程运行到了位置4;后面时刻,线程Y运行到了位置8(退出同步代码块),此时实例已经被创建了,线程X运行到位置4(进入同步块),如果不进行这第二重的判断,那么线程X又会再次创建一个实例。
这里的1为什么要添加关键字volatile?volatile的作用是告诉编译器不对代码的执行顺序进行优化,老老实实的执行。因为new Singleton()不是一个原子操作,具体比如是有三个步骤:
A申请一段内存空间;
B初始化相关变量;
C将引用指向这段内存空间(此时uniqueInstance才不为null)。
那么正常来说new Singleton()的执行顺序就是ABC,但是编译器为了提高效率,会对代码的执行顺序进行优化,比如优化成ACB顺序。如果new Singleton()按照ACB的顺序,那么问题就来了,比如线程Y执行new Singleton()到AC的时候,这个时候实例已经不为空了,但是由于B还没有执行,所以该实例还并未完全初始化,如果这个时候线程X运行到了2,那么它就不能运行到位置3,而是直接到位置9,然后返回该实例,就可能造成程序的不稳定或者其他异常情况发生。
静态初始化:非延迟创建
public class Singleton {
private static Singleton uniqueInstance = new Singleton(); //在静态初始化阶段就创建实例
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
防止反射
前面提到的方式都还存在一个问题:利用反射机制,可以调用私有方法,从而再次创建新的实例。
public class Singleton {
private static Singleton uniqueInstance = new Singleton(); //在静态初始化阶段就创建实例
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
public static void main(String arg[]) throws Exception{
Singleton fisrtObject = Singleton.getInstance();
Class objectClass = Singleton.class; //利用反射机制
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true); //获取私有构造器的访问权限
Singleton secondObject = (Singleton) constructor.newInstance(); //创建第二个实例
System.out.println(fisrtObject);
System.out.println(secondObject);
System.out.println(fisrtObject == secondObject);
}
}
runs.wang.test.Singleton@15db9742
runs.wang.test.Singleton@6d06d69c
false
不难发现,如果我们通过反射机制可以利用私有的构造器,进而创建新的实例,破坏掉单例模式。所以在我们上面的方式中,需要防止反射,具体地,可以对私有构造函数进行限制。
private Singleton() {
if(uniqueInstance != null) { //防止反射
throw new RuntimeException();
}
}
所以,正确实现单例模式的方式如下:
静态初始化方式
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
if(uniqueInstance != null) {
throw new RuntimeException();
}
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
同步加锁方式
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
if(uniqueInstance != null) {
throw new RuntimeException();
}
}
public static synchronized Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
同步加锁方式(优化版)
public class Singleton {
private static volatile Singleton uniqueInstance;
private Singleton() {
if(uniqueInstance != null) {
throw new RuntimeException();
}
}
public static Singleton getInstance() {
if(uniqueInstance == null) {
synchronized (Singleton.class) {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
枚举方式(实现单例模式推荐使用的方式)
public enum Singleton {
uniqueInstance;
}
e.g. 创建单例线程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public enum ThreadManager {
uniqueInstance;
private final int coreThreadNum = 16;
private final int maxThreadNum = 128;
private final long threadAliveTime = 120L;
private final TimeUnit timeUnit = TimeUnit.SECONDS;
private final ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1024);
private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
coreThreadNum,
maxThreadNum,
threadAliveTime,
timeUnit,
workQueue
);
public ThreadPoolExecutor getThreadPool() {
return threadPool;
}
public void execute(Runnable runnable) {
threadPool.execute(runnable);
}
public <T> Future<T> submit(Callable<T> callable){
return threadPool.submit(callable);
}
}
Reference
《Head First Design Patterns》