在多线程环境中的单例模式
目前有三种实现单例模式的方式:
- 饿汉模式
- 懒汉模式
- 静态内部类
1、饿汉模式
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
// 第二步 自行对外提供实例
private static final Singleton singleton = new singleton();
// 第三步 提供外界可以获取实例的方法
public static Singleton getInstance(){
return singleton;
}
}
在多线程的情况下,饿汉模式的性能是非常好的,因为获取实例的方法只是简单的返回实例,并没有任何的锁操作,因此在并行的程序中会有良好的表现。
但是这种方式有这明显的不足,就是实例在什么时候创建是不受控制的,对于静态成员singleton,它会在类第一次初始化的时候被创建。这个时刻并不一定是getInstance()第一次被调用的时候。
当类初始化的时候,实例就会被创建。如果希望精确控制singleton的创建时间,可以采用支持延迟加载策略的方法。这种方法只会在第一次调用getInstance()方法的时候才会创建对象。
2. 懒汉模式
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
// 第二步 创建实例的引用
private static final Singleton singleton = null;
// 第三步 提供外界可以获取实例的方法,此时需要判断一下引用的状态。
public static Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
懒汉模式的实现思想如下:最初,我们并不需要实例化singleton,而当getInstance()方法第一次被调用的时候,创建单例对象。
但是这个模式有一个特别明显的问题,就是没有考虑多线程下的安全问题,在多线程并发的情况下,会并发调用获取实例方法,从而导致系统同时创建多个单例类实例,显然不符合要求。可以通过给getInstance()方法加锁才行。
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
// 第二步 创建实例的引用
private static final Singleton singleton = null;
// 第三步 提供外界可以获取实例的方法,此时需要判断一下引用的状态。
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
添加synchronized锁虽然可以保证线程安全,但是锁的范围过大,单例类是系统唯一的,在创建出来实例后,后续再加锁就只会造成性能的浪费。需要对代码进行优化,所以又有了双重校验锁的模式。
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
// 第二步 创建实例的引用
private static final Singleton singleton = null;
// 第三步 提供外界可以获取实例的方法,此时需要判断一下引用的状态。
public static synchronized Singleton getInstance(){
// 第一次校验
if(singleton==null){
synchronized(Singleton.class){
// 第二次校验
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
singleton = new Singleton();并不是原子操作,所以双重校验锁会出现指令重排序问题。可能导致在实例还没又初始化完成之前,就被其他线程使用的问题,可以使用volatile关键字来修饰singleton字段,可以禁止指令的重排序优化。
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
// 第二步 创建实例的引用
private static volatile Singleton singleton = null;
// 第三步 提供外界可以获取实例的方法,此时需要判断一下引用的状态。
public static synchronized Singleton getInstance(){
// 第一次校验
if(singleton==null){
synchronized(Singleton.class){
// 第二次校验
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
3. 静态内部类
class Singleton{
// 第一步 构造器私有化
private Singleton(){}
//第二步 创建静态内部类
private static class help{
private static Singleton singleton = new Singleton();
}
// 第三步 提供外界可以获取实例的方法
public static Singleton getInstance(){
return help.singleton;
}
}
可见,双重检查模式是一种丑陋,复杂的方法,而在静态内部类实现方式却可以完美拥有前两种方式的优点,首先getInstance()方法中没有锁,这使得在高并发环境下性能优越。其次,只有在getInstance()才会创建实例,因为这种方法巧妙的使用了内部类和类的初始化方式,私有化内部类,使得我们不可能在外部访问并初始化它。而我们只能在getInstance()内部才能对内部类初始化,利用虚拟机的类初始化机制来创建单例。