单例模式之懒汉式线程不安全的原因?懒汉式怎样实现线程安全?

1、懒汉式-单线程版本

多线程不安全原因?
多线程情况下,多个线程同时执行到 if(SINGTON2==null)语句,创建多个引用对象。

  public class Sington {
   private static Sington SINGTON2;
    private Sington(){};
    public static Sington getInstance2() {
        if (SINGTON2 == null) {
            SINGTON2 = new Sington();
        }
        return SINGTON2;
    }
  }
2、懒汉式-多线程版-性能低-synchronized静态同步方法
 public class Sington {
    private static Sington SINGTON3;
    private Sington(){};
    public synchronized static Sington getInstance3() {
        if (SINGTON3 == null) {
            SINGTON3 = new Sington();
        }
        return SINGTON3;
    }
}
 

此方法性能低的原因?

  1. 比如main线程同时启动10个线程;

  2. 多线程下类加载SINGTON3==null;

  3. 调用getInstance3()方法,线程同步互斥;

  4. 第一个进入的线程,创建好对象,并返回;

  5. 此时对象已经实例化,但是后面的9个线程还是通过同步互斥获取对象,此过程可通过并发执行,但是现在是按序执行,效率低。

3、 懒汉式-多线程版-二次判断-未使用volatile
public class Sington {
private static Sington SINGTON4;

    private Sington(){};
    public static Sington getInstance4() {
        if (SINGTON4 == null) {
            synchronized (Sington.class) {
                if (SINGTON4 == null) {
                    SINGTON4 = new Sington();
                    //分解为三条指令
                    //分配对象的内存空间
                    //初始化对象
                    //对象赋值给引用变量
                }
            }
        }
        return SINGTON4;
    }
}

此方法会造成指令重排序。

什么是指令重排序:JVM编译JAVA文件为CLASS字节码文件时,CPU执行机器码时(JVM运行时解释字节码为机器码),会执行指令重排序。
是否可以重排序,与指令的前后依赖性有关。

例1:i++操作;

  1. 读:线程从主内存中读i的值,拷贝到到工作内存中;

  2. 修改:线程在工作内存中修改i的值+1;

  3. 写:线程把i修改后的值写入主内存中。

所以,此操作是非原子性操作。
例2:SINGTON4=new Sington();
该语句分解为三条指令:
(1) 分配对象的内存空间
(2) 初始化对象
(3) 对象赋值给引用变量(此时SINGTON4是非null的)

SINGTON4=new Sington() 这条语句是非原子性操作;
上述过程中存在指令重排序的优化,步骤一和步骤二不能进行指令重排序,因为这两条指令存在前后依赖性。可以先分配对象的内存空间,对象赋值给引用变量,初始化对象,第二步和第三步的顺序是无法保证的而导致出错。

举例:比如线程A和线程B并发执行,线程A执行分配对象的内存空间,对象赋值给引用变量操作。 这时时间片结束了,线程B执行if(SINGTON4==null)语句,发现SINGTON4!=null,返回对象 。 此时对象还没有初始化,会出现问题。线程A线程安全,线程B线程不安全。

如果一个变量的操作不会分解为多条指令,那么就是线程安全的;否则线程不安全。
4、 懒汉式-多线程版-二次判断-性能高-使用volatile
public class Sington {
private static volatile Sington SINGTON5;
private Sington(){};
    public static Sington getInstance5() {
        if (SINGTON5 == null) {
            synchronized (Sington.class) {
                if (SINGTON5 == null) {
                    SINGTON5 = new Sington();
                }
            }
        }
        return SINGTON5;
    }
}
volatile关键字作用:
  • 保证可见性(变量都在主存进行操作)

  • 禁止指令重排序,建立内存屏障;

  • 不能保证原子性。

可见性,是指线程之间的可见性,一个线程修改共享变量的值,对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。

代码分析:

加上volitate之后,指令的执行顺序为:分配对象的内存空间;初始化对象;对象赋值给引用变量;步骤一和步骤二因为存在依赖性,所以不能交换顺序,步骤三SINGTON5被volatile修饰,不会重排序。

举例:比如线程A和线程B并发执行,线程A正在执行,这时时间片结束了;线程B执行if(SINGTON4==null)语句。

  1. 假如if语句在步骤一和步骤二之间执行,此时SINGTON4为null,不影响。

  2. 假如if语句在步骤二和步骤三之间执行,此时SINGTON4为null,不影响。

  3. 假如if语句在步骤三之后执行,此时SINGTON4为非null,引用变量都已经指向对象了,也不影响。

    综上所述,if语句插入任意指令之间,线程安全。

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值