Java多线程之可见性分析

可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。


Java内存模型(JMM)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存中和从内存中读取出变量这样的底层细节。


多线程中所有的变量都存储在主内存中,每个线程都有自己的独立的工作内存,里面保存该线程使用的变量的副本(主内存中该变量的拷贝)。

两种方式实现多线程的可见性

synchronized实现可见性

synchronized在多线程中有两种主要的功能

  • 原子性(同步)
  • 可见性

JMM关于synchronized的两条规定:

  1. 线程解锁前,必须把共享变量的最新值刷新到主内存中
  2. 线程加锁时,将清空工作内存中共享的变量值,从而使用共享变量时需要从主内存中重新读取最新的值(PS:加锁与解锁需要的是同一把锁)。实现共享变量的可见性。

重排序:编译器或处理器为了提高程序性能而作的优化。
as-if-serial:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(Java编译器运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)。


导致线程共享变量在线程中不可见的原因分析:
  1. 线程交叉执行 —->synchronized的原子性解决
  2. 重排序结合线程交叉执行 —->synchronized的原子性解决
  3. 共享变量更新后的值没有在工作内存与主内存间及时更新 —->synchronized的可见性解决

volatile实现可见性

ps:能够保证变量的可见性,不能保证变量符合操作的原子性。
volatile实现原理分析:

通过加入内存屏障和禁止重排序优化来实现的

  • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令(强制刷入主内存)
  • 对volatile执行读操作时,会在读操作前加入一条load屏障(强制从主内存中更新到工作内存中)

eg:num++;包含三步操作,volatile无法保证原子性,多线程操作时,虽然保留了可见性,但无法保持同步。
解决方案:

  1. 使用synchronized关键字
  2. 使用ReentrantLock加锁操作(锁内部操作需要在try…finally中执行。finally中执行unlock()解锁操作)
  3. 使用AtomicInterger

要在多线程中安全使用volatile变量,必须满足

  1. 对变量的写入操作不依赖当前变量值
  2. 该变量没有包含在其他变量的不变式中

synchronized与volatile比较

  • volatile不需要加锁,更轻量级,不会阻塞线程
  • 从可见性角度分析:volatile读相当于加锁,volatile写相当于解锁
  • synchronized既能满足原子性又能满足可见性

你可能感兴趣的:(Java,多线程,可见性)