单例模式

-------懒汉模式---------
单例实例在第一次调用的时候创建
public SingleExample{
//构造函数设为private,是防止该类使用new SingleExample生成
private SingleExample(){};
private static SingelExample instance;
public static SingleExample getSingleExample(){
if( instance == null ){
instance = new SingleExample();
}
return instance;
}
}

该模式创建的实例,在单线程场景下没有问题,但是在多线程的场景下,会有问题,问题出在
if( instance == null ){
instance = new SingleExample();
}

一个线程判断出instance是null后,去实例后的同时,另一个线程也判断出null也去实例化,这样就会出现线程安全问题。
所以懒汉模式的实例是线程不安全的

-------饿汉模式---------
public class SingeExample2{
private SingleExample2(){};
private static SingleExample2 instance = new SingleExample2();
public static getSingleExample2(){
return instance;
}
}
该模式是在类加载的时候生成单例实例。是线程安全的。
但是不足是如果在构造方法中有过多的逻辑,会导致加载卡,还有实例最后没有使用的话,会有导致资源浪费的情况发生。

-------双重锁检测模式---------
public class SingleExample{
private SingleExample(){};
private static SingleExample instance = null;
public static SingleExample getSingleExample(){
if( instance == null){
synchronized(SingleExample.class){ //同步锁
if(instance == null){
instance = new SingleExample();
}
}
}
return instance;
}
}

这种写法也存在线程安全问题。因为涉及到指令重排,先说代码是怎样被执行的:
instance = new SingleExample();
以这行代码为例:
1、allocate memory 分配内存空间
2、初始化对象
3、instance 指向刚刚分配的内存地址
这三步中,第二步和第三步其实是没有严格的顺序,Jvm和cpu会优化,进行指令重排。
所以可能的执行顺序是这样:
1、allocate memory 分配内存空间
3、instance 指向刚刚分配的内存地址
2、初始化对象
3放到了2的前面,这样就会发生这样的情况:
在这里插入图片描述
A线程先执行到了锁里面,B线程执行到了第一处判空位置。而这个时候A线程实例化对象的时候,先执行的第三步(instance执行内存地址,却没有实例化对象),那么B线程一看不为空了,就直接返回instance,这样就存在问题,instance只是指向了内存地址,却没有实例,导致后续操作出现问题,比如报空指针等。

所以双重检测机制有线程安全的问题,这个问题藏的很深,很难被发现。双重检测机制是由指令重排引发的线程不安全,那么怎么解决呢,当然是不让JVM或CPU进行指令重排,而volatile关键字可以禁止jvm和cpu进行指令重排

所以对双重检测机制进行如下优化即可:
public class SingleExample{
private SingleExample(){};
private volatile static SingleExample instance = null;
public static SingleExample getSingleExample(){
if( instance == null){
synchronized(SingleExample.class){ //同步锁
if(instance == null){
instance = new SingleExample();
}
}
}
return instance;
}
}

这样,使用双重检测机制+volatile 创建出来的单例是没有问题的。

以上是懒汉模式创建单例对象的

还可以对饿汉模式进行优化来创建单例对象,如下:
public class SingleExample{
private SingleExample(){}
private static SingleExample instance = null;
static{
instance = new SingleExample();
}
public static SingleExample getSingleExample(){
return instance;
}
}

枚举创建单例

public class SingleExample{
private SingleExample(){}
private static SingleExample getSingleExample(){
SingleTonEnum.INSTANCE.getInstance();
}
private enum SingleTonEnum{
INSTANCE;
private SingleExample singleton = null;
SingleTonEnum(){ //虚拟机会绝对的只执行一次枚举的构造方法
singleton = new SingleExample();
}
public SingleExample getInstance(){
return singleton;
}
}
}

这样也是线程安全的,同时也是比较推荐的创建单例的方法,这个方法其实是用到了枚举的构造方法只会执行一次的特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值