单例模式中懒汉模式的非线程安全问题的解决方法
程序员文章站
2022-05-01 23:30:32
...
非原创,转自https://blog.csdn.net/noaman_wgs/article/details/53258710
这里为什么要用volatile修饰instance?
还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。
单例模式中懒汉模式的非线程安全问题的解决方法
单例模式中有饿汉模式和懒汉模式两种。饿汉模式也叫立即加载 ,即在get之前就已经创建实例instance;
-
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
哈希值相同,是单例模式的实现。但这种加了synchronized关键字后,多线程进行操作时只能等一个线程处理完,下一个线程才能进入同步方法中,效率低下。
二、同步代码块(效率低)
-
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;
-
}
-
}
还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。
上一篇: php执行多条shell命令