可见性的概念
一个线程对共享变量值的修改,能够及时地被其他线程看到
共享变量值:如果一个变量在多个线程的工作内存中都使用,存在副本,那么这个变量就是这几个线程的共享变量
两条重要规定:
- 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
- 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值得传递需要通过主内存来完成
JMM(Java Memory Model)
- 所有的变量都存储在主内存中
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
共享变量可见性实现原理
线程1把工作内存1中更新过的共享变量刷新到主内存中,主内存把最新的共享变量的值更新到工作内存2中,线程2就能及时看到线程1的修改
synchronize实现可见性
- JMM关于synchronize的两条规定
线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁是,将清空工作内存中共享变量的值,从而使用共享内存时,需要从主内存中重新读取最新的值(加锁和解锁需要同一把锁) - 重排序 as-if-serial
volatile实现可见性
- volatile关键字:
能够保证volatile变量的可见性
不能保证volatile变量复合操作的原子性 - volatile如何实现内存可见性
通过加入内存屏障和禁止重排序优化来实现
对变量执行写操作时,会在写操作后加入一条store屏障指令
volatile 变量执行度操作时,会在读操作前加入一条load屏障指令 - volatile不能保证原子性
当a线程的工作变量已经读过变量的值后,即使b线程改变变量的值,并store到内存中,也不会触发a线程load,因为a线程读取时已经load过了。
- 保证变量自增操作的原子性
使用synchronize关键字
使用ReentrantLock(java.until.concurrent.locks)
使用AtomicInterger
(vava.util.concurrent.atomic) - volatile适用场合
同时满足:
- 对变量的写入操作不依赖其当前值,
不满足:number++,count=count*5等
满足:boolean变量,记录温度变化的变量等 - 该变量没有包含在具有其他变量的不变式中
不满足:不变式low<up
final也可以保证内存可见性
synchronize 和 volatile 比较
volatile不需要加锁,不会阻塞线程
从内存可见性角度讲volatile读相当于加锁,volatile写相当于解锁
synchronize既可以保证可见性和原子性