1.解决什么问题
2.实现机制
3.使用场景
-
解决什么问题?
- 内存可见性
在执行变量写操作后,执行lock指令,这个指令将变量实时写入到主内存而不是处理器的缓存中,然后其他处理器通过缓存一致性协议嗅探到这个变量的变更,将该变量的缓存设置为无效,从而实现内存可见性。public class VolatileTest { public static void main(String[] args) throws InterruptedException { Task task = new Task(); Thread t1 = new Thread(task, "线程t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println("开始通知线程停止"); task.stop = true; //修改stop变量值。 } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程t2"); t1.start(); //开启线程t1 t2.start(); //开启线程t2 Thread.sleep(1000); } } class Task implements Runnable { // volatile boolean stop = false; int i = 0; @Override public void run() { long s = System.currentTimeMillis(); while (!stop) { i++; } System.out.println("线程退出" + (System.currentTimeMillis() - s)); } }
- 禁止代码重排序 重排序分类:
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
-
实现机制
volatile的两条实现原则1. Lock前缀指令会引起处理器缓存回写到内存 2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
-
volatile不保证原子性
package com.pimee.thread.volatiles; public class VolatileDemo2 { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new ThreadTest().start(); } } } class ThreadTest extends Thread{ private static volatile int count; @Override public void run() { for (int i = 0; i < 10000; i++) { // synchronized (ThreadTest.class){ count++; // } } System.out.println(Thread.currentThread().getName() + " count=" + count); } }
-
使用场景
- 写入变量值不依赖变量的当前值时。因为如果依赖当前值,将是获取—计算—写入三步操作,这三步操作不是原子性的,而volatile不保证原子性。
- 读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile的。