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

  • 缓存一致性协议
    • 协议可以保证每个缓存中使用的共享变量的副本是一致的,原理:CPU对主存中的共享变量有写入操作时,会立即通知其他CPU将该变量缓存行置为无效状态 。其他CPU发现该变为无效状态时,就会重新去主存中读取该变量最新值 。
    • 优点就是可以解决问题,读多写少效率还OK;缺点就是实现繁琐,较耗费性能,在对于写多的场景下效率很不可观
  • ? 问题:线程为什么会不安全?
    ?答:共享资源不能及时同步更新,归根于 分时系统上下文切换时 指令还未执行完毕 (没有写回结果) 更新异常
    引入并解释并发编程特性?众所周知现在的互联网大型项目,都是采用分布式架构同时具有其“三高症状”,高并发、高可用、高性能 。高并发为其中最重要的特性之一,在高并发场景下并发编程就显得尤为重要,其并发编程的特性为原子性、可见性、有序性 。
    原子性指的是一个或多个操作要么全部执行成功要么全部执行失败,期间不能被中断,也不存在上下文切换,线程切换会带来原子性的问题 。
    • 变量赋值问题:
      • 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 关键字?

      经验总结扩展阅读