返回

单例模式中懒汉模式的非线程安全问题的解决方法

发布时间:2023-10-17 20:51:44 128

单例模式中懒汉模式的非线程安全问题的解决方法

package singleton1;

//饿汉模式(立即加载)
public class Singleton {

private static Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
return instance;
}
}

 

 

 

而懒汉模式也叫延迟加载,即在get时才会被创建实例。懒汉模式在多线程的环境中就会出现多实例的情况,与单例模式相背离。如下代码可证:

 

 

package singleton2;
//懒汉模式
public class Singleton {

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance(){
try {
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

创建线程:

 

package singleton2;

public class ThreadA extends Thread{

@Override
public void run(){
System.out.println(Singleton.getInstance().hashCode());//根据实例对象哈希值是否相同来判断对象是否相同
}

}

创建运行类:

 

package singleton2;

public class Run {

public static void main(String[] args) {
ThreadA t1=new ThreadA();
ThreadA t2=new ThreadA();
ThreadA t3=new ThreadA();
t1.start();
t2.start();
t3.start();
}

}

运行结果:

 

1321522194
2134502363
866891806

哈希值不同,所以对象不同,即不是单例模式。如何解决这个问题呢,下面提供了几种方法:

 

 

 

 

一、方法上声明synchronized关键字(效率低)

 

 

package singleton2;
//懒汉模式
public class Singleton {

private static Singleton instance = null;

private Singleton(){}
//1 方法上声明synchronized关键字
synchronized public static Singleton getInstance(){
try {
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

 

 

运行结果如下:

 

1876189785
1876189785
1876189785

 

 

 

二、同步代码块(效率低)

 

 

package singleton3;
//懒汉模式
public class Singleton {

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance(){
try {
// 2 使用同步代码块,但效率低
synchronized(Singleton.class){
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

 

 

运行结果:

 

 

1619327594
1619327594
1619327594

       使用同步代码块方式也能解决上述问题,虽然不需要判断实例是否为null,但同样的也会带来效率低的毛病。

 

 

三、使用DCL(Double-Check Locking)双检查锁机制---(推荐)

 

 

public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}

public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

 

这里为什么要用volatile修饰instance?

原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:

 

1)分配对象的内存空间:memory = allocate();

 

2)初始化对象:ctorInstance(memory);

 

3)指向分配的地址:instance =memory

 

多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。

 

加上volatile就可以避免2 3步重排序来保证线程安全。

 

 

 

四、使用静态内部类来实现单例模式

 

 

package singleton6staticinnerclass;

public class Singleton {

//静态内部类进行初始化
private static class SingletonHandler{
private static Singleton instance = new Singleton();
}

private Singleton(){}

public static Singleton getInstance(){
return SingletonHandler.instance;
}
}

 

五、static代码块来实现单例模式

 

 

package singleton7static;

public class Singleton {

private static Singleton instance = null;

private Singleton(){}
//静态代码块
static{
instance=new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}

 

还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。

 

 

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
一些几个经典的postgresql的sql语句 2023-10-17 17:59:37