JUC-线程通信

91 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

线程间通信

Java多线程的等待/通知机制是基于 Object 类的 wait() ⽅法(wait方法会释放线程所持有的锁)和 notify或者notifyAll方法来实现的。

那么java中线程的通信与同步是如何实现的呢?——Java内存模型(JMM)

JMM

JMM即Java Memory Model,它定义了主存,工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。

JMM体现在以下几个方面

  1. 原子性-保证指令不会受到线程上下文切换的影响
  2. 可见性-保证指令不会受cpu缓存的影响
  3. 有序性-保证指令不会受cpu指令并行优化的影响

  • 线程访问共享变量(堆中数据)时,并不是直接访问,而是要经过JMM
  • 每个线程都保存了一份该线程需要使用到的共享变量副本,于是当线程A与线程B要通信,需要做以下步骤:
    • 线程a将本地内存的共享变量副本值更新到主内存
    • 线程b从主内存读取更新后的共享变量

总结: JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证

volatile关键字

volatile主要有以下两个功能:

  1. 保证变量的内存可见性(控制线程到主存中重新获取值)
  2. 保证有序性,禁止并行时指令重排序 (读写屏障)
  3. 注意! volatile并不保证原子性,也就是说,volatile不能阻止指令交错

我们来看看volatile 如何实现内存可见性

public class demo8volatiledemo {
    int a = 0;
    volatile boolean flag = false;
    public void writer() {
        a = 1; // step 1
        flag = true; // step 2
    }
    public void reader() {
        if (flag) { // step 3
            System.out.println(a); // step 4
        }
    }
}
  • 当运行step2时, JMM会立即把该线程对应的本地内存中的共享变量的值刷新到主内存
  • 当运行step3时, JMM会把立即该线程对应的本地内存置为⽆效,从主内存中读取共享变量的值。

在保证内存可⻅性这⼀点上,volatile有着与锁相同的内存语义,所以可以作为⼀个 “轻量级”的锁来使⽤。但由于volatile仅仅保证对单个volatile变量的读/写具有原子性,⽽锁可以保证整个临界区代码的执⾏具有原子性。

所以在功能上,锁比volatile更强⼤;在性能上,volatile更有优势

而且volatile更适合用在一个线程写,多个线程读的情景