可见性含义
只是一个线程修改了某个变量的值,在另一个线程中能够读到这个修改后 的值
保持可见性的方式
通过以下几种方式实现可见性
- 锁
- volatile
锁(synchronized,Lock)通过线程阻塞的方式实现可见性。我们知道,在激烈竞争下,synchronized会升级为重量级锁,严重影响性能,相反的,volatile被称之为轻量级锁,在某些场景下,能够有效提升性能。
volatile
在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀 的指令,该前缀的作用,
- 将当前处理器缓存行的数据写回系统内存
- 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
CPU在处理数据的时候,会将内存数据加载到CPU的高速缓存中进行计算,以此提升效率,但操作完不知道何时会写到内存。但是,使用volatile修饰的变量,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。
写到内存还不行,得需要其他CPU知道这个事情,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
使用volatile可以避免,在多线程条件下,不能让其他线程出现脏读
,如果没有多线程访问,那么不需要使用volatile,让CPU自行决定何时写回内存。
有了volatile为何还用锁?
volatile可以实现轻量级锁,性能上优于锁,但是也不能过度使用volatile。
另外,volatile是变量修饰符,只能使用修饰变量,具有可见性,有序性(禁止重排),但是不具有原子性。言外之意,即使volatile修饰了某个变量,这个变量在其他方法中的操作不具有原子性,volatile无法控制其他方法中操作的共享问题。此时,锁的作用在这。
使用场景,
- 适用于对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量。
- 适用于读多写少的场景。
- 可用作状态标志。
JDK中volatie应用:JDK中ConcurrentHashMap的Entry的value和next被声明为volatile,AtomicLong中的value被声明为volatile。AtomicLong通过CAS原理(也可以理解为乐观锁)保证了原子性。