在开发过程中,其实很多情况下,都需要用到单例模式来维持对象的唯一性。
比如线程池、数据源、sessionFactory等。
一般的做法(懒汉式):
public class MyClass{
//饿汉式是直接声明变量是就初始化:
//private static MyClass myClass=new MyClass();
private static MyClass myClass;
//将构造器声明为私有的,不允许外部类创建实例对象
private MyClass(){}
//声明一个静态方法来返或一个单例对象
public static MyClass getInstance(){
if(myClass == null)
myClass = new MyClass();
return myClass;
}
}
但是这个会在多线程下回导致数据不一致性。
这样线程一跟线程二就获得不同的实例对象,打破了唯一性。
这个问题就变为多线程问题,一般通过加锁是可以解决问题的,比如:
public class MyClass{
private static MyClass myClass;
//将构造器声明为私有的,不允许外部类创建实例对象
private MyClass(){}
//声明一个静态方法来返或一个单例对象
//通过增加synchronized来保证同一时刻只有一个线程可以执行getInstance()函数
public static synchronized MyClass getInstance(){
if(myClass == null)
myClass = new MyClass();
return myClass;
}
}
但是这种加锁方法会带来性能问题,因为其实对于单例模式,只有在第一次初始化myClass的时候需要控制只有一个线程可以进行实例化对象,对于之后的获得对象,由于已经实例化了,是可以直接返回的,但是由于synchronized的声明就降低了性能。
这样就引出了“双重检查加锁”概念,代码如下:
public class MyClass{
//增加volatile关键字来修饰"实例变量"
private volatile static MyClass myClass;
//将构造器声明为私有的,不允许外部类创建实例对象
private MyClass(){}
//声明一个静态方法来返或一个单例对象
//通过增加synchronized来保证同一时刻只有一个线程可以执行getInstance()函数
//将锁的粒度降低
public static MyClass getInstance(){
if(myClass == null){
synchronized(this.class){
if(myClass == null){
myClass = new MyClass();
}
}
return myClass;
}
}
这样就保证了只有当第一次需要实例化对象的时候才会用到synchronized控制并发。
其实看到这,很多人会有一个疑问,好像没必要用到volatile关键字啊。
我第一次看见也是这么觉得,后来查看了一下资料,发现了“新大陆”。
主要原因就是编译器为了一下优化操作,会执行指令重排。具体原因可以参考博客:双重检查锁定原理详解
当然单例模式还有很多不同的实现方式,比如枚举等。
以上内容为《大话设计模式》的学习笔记