从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例( 二 )


原子性指的是一个或多个操作要么全部执行成功要么全部执行失败,期间不能被中断,也不存在上下文切换,线程切换会带来原子性的问题 。

  • 变量赋值问题:
    • b 变量赋值的底层字节码指令被分为两步:第一步先定义 int b;第二步再赋值为 10 。
    • 两条指令之间不具有原子性,且在多线程下会发生线程安全性问题
      int b = 10;
可见性指的是当前线程对共享变量的修改对其他线程来说是可见的 。以下案例中假设不会出现多线程原子性问题(比如多个线程写入覆盖问题等),即保证一次变量操作底层执行指令为原子性的 。
例如上述变量在读写场景下,不能保证其可见性,导致写线程完成修改指令时但为同步到主存中 , 读线程并不能获得最新值 。这就是对于B线程来说没有满足可见性 。
  • 案例解析:final关键字
    • final 变量可以保证其他线程获取的该变量的值是唯一的 。变量指成员变量或者静态变量
    • b 变量赋值的底层字节码指令被分为两步:第一步先定义 int b;第二步再赋值为 10
      final a = 10;int b = 10;
    • final修饰的变量在其指令后自动加入了写屏障 , 可以保证其变量的可见性
    • a 可以保证其他线程获取的值唯一;b 不能保证其他线程获取到的值一定是 10 , 有可能为 0 。
    • 读取 final 变量解析 :
      • 不加 final 读取变量时去堆内存寻找,final 变量是在栈空间,读取速度快
      • 读取 final 变量时,直接将其在栈中的值复制一份 , 不用去 getstatic ,性能得到提升
      • 注意:不是所有被 final 修饰的变量都在栈中 。当数值超过变量类型的 MAX_VALUE 时,将其值存入常量池中
      • 读取变量的速度:栈 > 常量池> 堆内存
  • final 可以加强线程安全,而且符合面向对象编程开闭原则中的close,例如子类不可继承、方法不可重写、初始化后不可改变、非法访问(如修饰参数时,该参数为只读模式)等
有序性指的是程序执行的顺序按照代码的先后顺序执行 。
在Java中有序性问题会时常出现,由于我们的JVM在底层会对代码指令的执行顺序进行优化(提升执行速度且保证结果),这只能保证单线程下安全,不能保证多线程环境线程安全,会导致指令重排发生有序性问题 。
案例:排名世界第一的代码被玩坏了的单例模式
DCL(double checked):加入 volatile 保证线程安全,其实就是保证有序性 。
上代码:其中包括了三个问题并且有详细注释解释 。(鸣谢itheima满一航老师)
  1. 为什么加入 volatile 关键字?
  2. 对比实现3(给静态代码块加synchronized) 说出这样做的意义?
  3. 为什么要在这里加空判断,之前不是判断过了吗?
final class SingletonLazyVolatile {private SingletonLazyVolatile() { }// 问题1:为什么加入 volatile 关键字?// 答:防止指令重排序 造成返回对象不完整 。如 TODOprivate static volatile SingletonLazyVolatile INSTANCE = null;// 问题2:对比实现3(给静态代码块加synchronized) 说出这样做的意义?// 答:没有锁进行判断、效率较高public static SingletonLazyVolatile getInstance() {if (INSTANCE != null) {return INSTANCE;}// 问题3:为什么要在这里加空判断,之前不是判断过了吗?// 答:假入t1 先进入判断空成立,先拿到锁 ,  然后到实例化对象这一步(未执行)//同时 线程 t2 获取锁进入阻塞状态,若 t1 完成创建对象后 , t2 没有在同步块这进行判空,t2 会再新创建一个对象,//导致 t1 的对象被覆盖 造成线程不安全 。synchronized (SingletonLazyVolatile.class) {// t1if (INSTANCE != null) {return INSTANCE;}INSTANCE = new SingletonLazyVolatile();// t1这行代码会发生指令重排序,需要加入 volatile// 如:先赋值指令INSTANCE = new SingletonLazyVolatile , 导致实例不为空,下一个线程会判空失败直接返回该对象// 但是构造方法()指令还没执行,返回的就是一个不完整的对象 。return INSTANCE;}}}通过对并发编程的三要素介绍,也就是说,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性 。只要有一个没有被保证,就有可能会导致程序运行不正确 。
补充volatile知识: