1. 先讲点理论的知识:
volatile 关键字使用场景:一个线程写,多个线程读。性质:保证可见性,但不是原子性的。
从jvm的内存模型来看,jvm线程有自己的本地内存,相当于是一个缓存。线程从主内存中取变量放到本地内存中,之后读取的都是本地内存中的值,而使用volatile修饰的变量,会强制线程从主内存中读取变量的值。
并发编程网中写道:“本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。”
volatile关键字使用了memory barrier技术,线程写变量时,会把CPU cache中的内容刷到主内存中,而线程读变量时,会从主内存中读取。我的感觉是:就是不使用缓存。
2. 再看看一个实际的例子:
static boolean flag = false; public static void main(String[] args) throws InterruptedException { final Thread loop = new Thread(new Runnable() { @Override public void run() { while(!flag) { System.out.println("loop thread:" + System.nanoTime()); } } }); Thread setter = new Thread(new Runnable() { @Override public void run() { flag = true; try { loop.join(); //等循环线程执行完,再退出 } catch (InterruptedException e) { e.printStackTrace(); } } }); loop.start(); Thread.sleep(10); setter.start(); }
网上有帖子说,上面的代码有可能产生死循环,但可能性很小。于是写了个dos脚本,执行程序1000次,也没发生死循环,所以我的理解是,loop线程并不会产生死循环,只是不能及时地读取到 flag 的值。
@echo off for /l %%i in (1,1,1000) do ( @echo start ... java -jar volatile.jar ) pause
那么,实现共享变量的可见性,除了volatile关键字外,还有其他方式吗?答案是synchronized关键字和Lock,网上说在释放锁时,会把共享变量的值刷新到主内存。
3. 结合 AtomicInteger 谈 volatile
private volatile int value;
AtomicInteger类的value字段是volatile的,这个类的作用是:在不加锁的情况下,去实现并发读写共享变量。
假定value没有用volatile修饰,这时一个线程使用cas修改值成功,但是因为没有加锁,value值不会刷到主存中,其他线程就有可能读取到缓存值(并未修改的值)。