分类
单例模式分为懒汉与饿汉
懒汉:懒加载,延迟创建对象
饿汉:提前创建对象
实现步骤
1.私有化构造方法
2.对外提供一个获取单例的方法
单例模式(懒汉)
第一种方式
/**
* 单例模式-懒汉
* 实现步骤1.私有化构造函数 2.对外提供一个获取实例的方法
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 私有化构造方法
*/
private SingletonLazy(){
}
/**
* 多线程不安全
* @return
*/
public static SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
多线程调用时不安全,不能保证单例
想要保证单例,最简单的方法就是对getInstance方法加锁
/**
* 单例模式-懒汉
* 实现步骤1.私有化构造函数 2.对外提供一个获取实例的方法
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 私有化构造方法
*/
private SingletonLazy(){
}
/**
* 加锁,保证单例
* @return
*/
public static synchronized SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
这样可以保证线程安全与单例,但是使用synchronized 会有较大的性能开销,为保证性能,我们可以尝试减小锁的粒度,不对整个方法加锁,只对创建对象的代码块加锁
/**
* 单例模式-懒汉
* 实现步骤1.私有化构造函数 2.对外提供一个获取实例的方法
*/
public class SingletonLazy {
private static SingletonLazy instance;
/**
* 私有化构造方法
*/
private SingletonLazy() {
}
/**
* 双重校验锁
*
* @return
*/
public static SingletonLazy getInstance() {
//第一重校验
if (instance == null) {
synchronized (SingletonLazy.class) {
//第二重校验
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
这样仍然是不安全的,因为创建对象的instance = new SingletonLazy()并不是一个原子操作,该操作共分三步
1.在内存中为对象分配空间
2.在分配好的空间中创建对象
3.将该对象的引用(地址)的值赋值给instance
对于这三个步骤的执行顺序,JVM是可能会进行指令重排的,指令重排是JVM对语句执行的优化,只要语句之间没有依赖,JVM就有权对语句进行优化。所以执行的顺序可能不是123,可能是132,那么假设有如下的情况出现:
有两个线程,线程1与线程2调用getInstance方法,这里假设创建对象时按132的步骤执行,当线程1执行到3时(将对象引用赋值给instance),此时instance就不是null了,此时线程2执行到第一个if判断,instance不是null,线程2会执行最后的return instance,那么线程2拿到的instance是一个未创建的对象,这肯定会导致程序出问题的,所以在这里需要禁止指令重排,可以使用volatile修饰instance以禁止指令重排。
那么当我们用synchronized修饰getInstance方法时,同样是执行了instance = new SingletonLazy()的,为什么不用考虑指令重排的问题呢,首先双重校验锁的那种写法有问题的原因是当创建对象的操作被JVM指令重排后执行到一半有另一个线程拿到了未创建完成的对象,而用synchronized修饰getInstance方法在一个线程释放锁之前另一个线程是不能执行该方法的,所以先拿到锁的线程可以执行完整个对象创建的代码,等另一个线程拿到锁的时候获取到的就是一个创建完成的instance对象,所以不会出现问题。
双重校验锁的安全写法
/**
* 单例模式-懒汉
* 实现步骤1.私有化构造函数 2.对外提供一个获取实例的方法
*/
public class SingletonLazy {
//volatile修饰instance,禁止指令重排
private static volatile SingletonLazy instance;
/**
* 私有化构造方法
*/
private SingletonLazy() {
}
/**
* 双重校验锁
*
* @return
*/
public static SingletonLazy getInstance() {
//第一重校验
if (instance == null) {
synchronized (SingletonLazy.class) {
//第二重校验
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
单例模式(饿汉)
饿汉即提前创建好对象
/**
* 单例模式-饿汉
*/
public class SingletonHungry {
//类加载时就创建对象
private static SingletonHungry instance = new SingletonHungry();
/**
* 私有化构造方法
*/
private SingletonHungry(){
}
public static SingletonHungry getInstance(){
return instance;
}
}
优点:简单,没有多线程同步问题
缺点:不管是否调用,instance都会被创建,占用一段内存
如果对象不大,并且创建不复杂,直接使用饿汉
其它情况使用懒汉