基础 | 并发编程 - [导论 & volatile]

并发编程

并发编程的特性

  • 原子性
    一个或一组操作在执行过程中不会被其他操作插入或中断
  • 可见性
    一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改
  • 有序性
    程序执行的顺序按照代码的先后顺序执行

先行发生原则(Happens-Before)

  • 若 java 内存中,操作 A 先行发生于 操作 B ,则 操作 B 可以观测到 操作 A 产生的影响
  • 用于判断数据是否存在线程竞争或是否线程安全

java 语法天然支持下面 8 个先行发生场景
下面8个场景中,操作 A 先行发生于操作 B 时,操作 B 可以观测到 操作 A 的结果

  • 程序次序规则(Program Order Rule)
    一个线程内,流程控制上在前的操作先行发生于流程控制中在后的操作时
  • 管程锁定规则(Monitor Lock Rule)
    一个锁的 unlock 操作 先行发生于 此锁的 lock 操作时
  • volatile 变量规则(Volatile Variable Rule)
    一个 volatile 变量的写操作 先行发生于对于 这个变量的读操作之前时
  • 线程启动规则(Thread Start Rule)
    一个线程 start()方法 先行发生 此线程其他方法
  • 线程终止规则(Thread Termination Rule)
    一个线程的 所有其他操作 先行发生 此线程的终止检测(join() 和 isAlive())时
  • 线程中断规则(Thread Interruption Rule)
    一个线程的 interrupt() 方法调用先行发生 此线程的终止检测(interrupt())时
  • 对象终结规则(Finalizer Rule)
    一个对象的 构造方法执行结束 先行发生于 此对象 finalize() 方法开始时
  • 传递性(transitivity
    操作A 先行发生于 操作B
    操作B 先行发生与 操作C
    操作A 先行发生于 操作C

volatile 关键字

volatile 关键字是 java 虚拟机提供的轻量级(线程间)同步机制,这意味着 volatile

  • 保证可见性
  • 不保证原子性
  • 禁用指令重排

指令重排
为提高性能,编译器和处理器会对指令进行重排
包括编译器优化重排指令并行重排内存重排
单线程时,指令重排前后保持一致
多线程时,可能出现问题

多线程下指令重排问题示例
说明
假设只有一个 AAA 对象
有两个线程,线程 1 调用 m1,线程 2 调用 m2

此测试程序编译并执行多次,
每次都是 m1 的第一句执行后,m2 开始执行

m1 先执行 a=1 时,输出 6
m1 先执行 f=true 时,输出 5

class  AAA{
    int a = 0;
    boolean f = false;

    public void m1(){
        a = 1;
        f = true;
    }

    public void m2(){
        if (f){
            a = a+5;
            System.out.println(a);
        }
    }
}

内存屏障(Memory Barrier)
内存屏障是一个 cpu 指令,作用如下

  • 内存屏障前后的指令不能与之调换顺序,因此前后指令也不会调换顺序
  • 内存屏障可以强制刷新 cpu 的缓存数据

JVM 指令重排的规定

是否允许指令重排 后 普通读 后 普通写 后 volatile 读 后 volatile 写
先 普通读 ×
先 普通写 ×
先 volatile 读 × × × ×
先 volatile 写 × ×

概括的讲,下面场景禁用指令重排

  • 先 volatile 读时
  • 后 volatile 写时
  • 先 volatile 写后读时

JMM 内存屏障策略
JMM 在 volatile 读写前后添加屏障如下面两组

volatile 写组

  • 写写屏障(StoreStore)
  • volatile 写
  • 写读屏障(StoreLoad)

volatile 读组

  • volatile 读
  • 读读屏障(LoadLoad)
  • 读写屏障(LoadStore)

上面两组直接与普通读写自由前后组合,如先 volatile 读,后 volatile 写可以等效为

  • volatile 读
  • 读读屏障(LoadLoad)
  • 读写屏障(LoadStore)
  • 写写屏障(StoreStore)
  • volatile 写
  • 写读屏障(StoreLoad)
是否允许指令重排 后 普通读 后 普通写 后 volatile 读 后 volatile 写
先 普通读 - - - 写写屏障(StoreStore)
先 普通写 - - - 写写屏障(StoreStore)
先 volatile 读 读读屏障(LoadLoad) 读写屏障(LoadStore) 读读屏障(LoadLoad) 写写屏障(StoreStore
先 volatile 写 - - 写读屏障(StoreLoad) 写读屏障(StoreLoad)

volatile 的适用场景
单例
双重检验懒汉式(DCL)理论上有很小概率出现问题
语句 instance = new Singleton() 不是原子的
由 3 步组成

  • 在堆中分配内存空间
  • 初始化对象
  • 变量 instance 指向内存空间

若 2 、3 步发生指令重排,且出现并发问题
则因为引用了内存空间,内外两层校验检查时 != null
但此时返回的是一个未初始化完成的对象
可以使用 volatile 禁用指令重排
读写锁缓存
JUC 包中

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...