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值不会刷到主存中,其他线程就有可能读取到缓存值(并未修改的值)。

posted on 2017-03-27 12:32  偶尔发呆  阅读(112)  评论(0编辑  收藏  举报