单例设计模式
单例模式属于创建型设计模式,单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点
单例特性:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
常见实现方式
- 饿汉式
不管是否使用,都直接创建对象。
public class Single01 {
private static Single01 INSTANCE = new Single01();
private static Single01 getInstance(){
return INSTANCE;
}
}
//另一种形式,和上面一个意思
class Single00 {
private static Single00 INSTANCE;
static{
INSTANCE = new Single00();
}
private static Single00 getInstance(){
return INSTANCE;
}
}
- 懒汉式
等到使用的时候才创建对象。
问题:线程不安全
原因:如果一个线程判断为null后挂起,另一个线程也进来判断为null,这时就两个线程都会创建INSTANCE
public class Single02 {
private static Single02 INSTANCE;
private static Single02 getInstance(){
if(INSTANCE == null){
INSTANCE = new Single02();
}
return INSTANCE;
}
}
- 饿汉式–线程安全–粗粒度锁
缺点:锁粒度太粗,效率低
public class Single03 {
private static Single03 INSTANCE;
private static synchronized Single03 getInstance(){
if(INSTANCE == null){
INSTANCE = new Single03();
}
return INSTANCE;
}
}
- 饿汉式–细粒度锁
问题:线程不安全
原因:如果第一线程判断为null,进入if语句,但是没有上锁时被挂起,这时,第二个线程来了判断依然为null,然后上锁进入同步代码块创建实例,执行结束后释放锁,然后前一个线程拿锁继续执行,INSTANCE又创建一次
public class Single04 {
private static Single04 INSTANCE;
private static Single04 getInstance(){
if(INSTANCE == null){
synchronized(Single04.class){
INSTANCE = new Single04();
}
}
return INSTANCE;
}
}
- DCL(Double Check Lock)单例
通过双重判断来解决细粒度锁方式出现的问题
public class Single05 {
private static volatile Single05 INSTANCE;
private static Single05 getInstance(){
if(INSTANCE == null){
synchronized(Single05.class){
if(INSTANCE == null){
INSTANCE = new Single05();
}
}
}
return INSTANCE;
}
}
DCL单例中volatile的必要性
首先需要知道,java对象的创建步骤:
- 分配对象空间,成员变量赋为类型默认值
- 调用构造方法对成员变量初始化
- 将对象空间地址值赋给字面量
其次,需要了解volatile的作用:
- 保证可见性
- 禁止指令重排
如果不加volatile,那么在创建对象过程很可能发生以下结果:
- 线程1分配了对象空间,并赋为成员变量赋了类型默认值
- 因为指令重排,初始化和地址赋值两步被颠倒,所以先赋了地址而没有初始化
- 此时,线程2进入,判断发现对象不为null,那么就会拿着这个没有初始化的对象直接去使用,从而导致程序出错
所以volatile在DCL单例中是非常重要的,不可或缺!