谈到多线程,自然就扯到数据争用,也就自然转到对线程可见性的学习。
关键字
可见性:指的就是一个线程对共享变量的修改,能够及时被其他线程看到。
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
重排序:代码书写的顺序和实际执行的顺序可能不同,指令重排序是编译器或处理器为了提高程序性能而做的优化
- 编译器优化的重排序
- 指令集并行重排序
- 内存系统重排序
as-if-serial 指的是,无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致。 Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义。
Java内存模型(Java Memory Model):描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
- 所有的变量都存储在主内存中
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本
- 线程对共享变量的所有操作,都必须在自己的工作内存中进行,不能直接从主内存中读写
- 不同线程之间无法直接访问其他线程工作内存中的变量,线程简变量的传递需要通过主内存来实现
导致共享变量在线程间不可见的原因
- 线程的交叉执行
- 重排序结合线程交叉执行
- 共享变量共享后的值没能在工作内存和主内存之间即使更新
要实现共享变量的可见性,必须保证两点
- 线程修改后的共享变量值能够及时从工作内存刷新到主内存
- 其他线程能及时把共享变量的最新值从主内存更新到自己的工作内存中 Java从语言层面支持的可见性实现方式
- synchronized
- volatile
synchronized
synchronized用于实现原子性(同步)和可见性。 JMM关于synchronized的两条规定
- 线程解锁前,必须把共享变量的最新值刷新到主内存中
- 线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。(注意:加锁和解锁必须是同一把锁) 由此可见,线程解锁前对共享变量的修改在下次加锁时对其他线程是可见的。而synchronized的原子性,也就是锁,保证了线程不会在共享变量交叉执行,从而避免了可见性的问题。
volatile
- 能够保证volatile变量的可见性
- 不能保证volatile变量复合操作的原子性 深入来说,volatile通过加入内存屏障和禁止重排序优化来实现可见性
- 写操作时,会在写操作之后加入一条store屏障指令
- 读操作时,虎仔读操作之后加入一条load屏障指令 volatile变量的每次读写,都必须向主内存提交或更新,这样便实现了可见性。复合操作指的是num++这样的操作,先+1后赋值,如果线程交叉执行,很有可能就会出现未可见的问题。 使用场合
- 对变量的写入操作并不依赖其当前值:boolean
- 改变量没有包含在具有其他变量的不变式中:不满足 low<up