深刻研究volatile和AtomicReference

遇到的坑 :

今天在线上忽然出现了一个问题,查了半天缘由就是我使用的成员变量是一个bean,可是我在发生特殊状况的时候会重置这个bean,即便我加了volatile或者是AtomicReference,然而在并发环境下,其余线程仍是使用了bean中变量的老引用致使出现问题.java

由此衍生出了我对volatile和AtomicReference的研究.bash



1:volatile的做用

volatile关键字的主要做用有两个:

  • 防止指令重排序 : 讲人话就是防止编译后java会按照必定规则和把指令从新排序优化执行
  • 强制读主存 : 讲人话就是jvm虚拟机会有线程内存副本,线程间的共享变量可能没法察觉对方的变化.

【注意点】:volatile虽然能够保证线程间的可见性可是没法提供原子性操做,也就是须要加锁或者cas来保证原子性 这时候可使用synchronized加锁操做或者juc的Atomic来代替并发

下面的代码咱们就来测试一下volatile的线程可见性:

/***
 ** 这是一个测试的对象
/**
 public class AtomicReferenceExample {
    private static final ExecutorService POOL = Executors.newCachedThreadPool();
    private static final int THREAD_SIZE = 5;

    private int nInt = 0;
    private volatile int vInt = 0;
    private volatile Integer vInteger = 0;
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    private void nIntIncr() {
        nInt++;
    }

    private void vIntIncr() {
        vInt++;
    }
}
复制代码
/**
     * 测试volatile的可见性
     */
    private static void testVolatileView() {
        AtomicReferenceExample example = new AtomicReferenceExample();
        //设置nint监听线程
        for (int i = 0; i < THREAD_SIZE; i++) {
            POOL.execute(() -> {
                boolean flag = true;
                while (flag) {
                    if (example.nInt > 0) {
                        System.out.println("监听到nint值改变 time : " + System.currentTimeMillis());
                        flag = false;
                    }
                }
            });
            //设置vint监听线程
            POOL.execute(() -> {
                boolean flag = true;
                while (flag) {
                    if (example.vInt > 0) {
                        System.out.println("监听到vint值改变 time : " + System.currentTimeMillis());
                        flag = false;
                    }
                }
            });
        }

        System.out.println("提交更改");
        example.vIntIncr();
        example.nIntIncr();
        System.out.println("执行更改值完成 time : System.currentTimeMillis() +"  nint = " + example.nInt + "  vint = " + example.vInt); System.out.println("提交执行完毕"); } 复制代码

执行结果咱们是只会监听到vint的改变,若是给nint也加上volatile,那么nint也会被监听到


jvm

下面的代码咱们就来测试一下volatile没法保证原子性:

private static void testVolatilAtomic() throws InterruptedException {
        int threadSize = 30;
        AtomicReferenceExample example = new AtomicReferenceExample();
        CountDownLatch countDownLatch = new CountDownLatch(threadSize * 2);
        for (int i = 0; i < threadSize; i++) {
            POOL.execute(() -> {
                for (int j = 0; j < 5000; j++) {
                    example.vIntIncr();
                }
                countDownLatch.countDown();
            });
            POOL.execute(() -> {
                for (int j = 0; j < 5000; j++) {
                    example.getAtomicInteger().incrementAndGet();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        System.out.println("最终结果 vint = " + example.getVInt());
        System.out.println("最终结果 atomicInt = " + example.getAtomicInteger().get());
        POOL.shutdown();
    }
复制代码

执行结果咱们能够看到两个结果不一致,就是由于没有实现原子性,具体原理百度都能搜到,就是主ram和线程ram的各类读啊什么的问题了,有兴趣能够本身查下,这边就很少作阐述了ide



2:AtomicReference的做用

AtomicReference的源码很简单测试

  • 用volatile修改他的成员变量v,做用如上volatile的做用
  • 用cas去更新他的成员变量v,保证他的引用更新是原子性的


3:volatile修饰引用变量的疑问

其实AtomicReference内部的v也是用了volatile修饰的,就像我开头描述的问题那样,当volatile修饰引用类型的时候,到底能不能保证bean内部的成员变量也能够拥有可见性呢?优化

这个问题能够用下面的代码测试:ui

public class TestVolatile implements Runnable{
    class Foo {
        boolean flag = true;
    }

    private volatile Foo foo = new Foo();  

    public void stop(){
        foo.flag = false;
    }

    @Override
    public void run() {
        while (foo.flag){}
    }


    public static void main(String[] args) throws InterruptedException {
        TestVolatile test = new TestVolatile();
        Thread t = new Thread(test);
        t.start();

        Thread.sleep(1000);
        test.stop();
    }
}
复制代码

然而问题来了,这段代码在网上有些人测试会一直执行,为变量flag加上volatile才会立刻退出,可是有的人说无论加没加都会立刻退出,好像是由于不一样版本不一样厂商的虚拟机,执行策略都不同.atom

我最上面写的那个问题也是基于这个疑惑,是否volatile修饰引用变量的时候,引用内部的变量是否可以保证绝对的可见性? 不知道有没有大佬可以指教回答!!spa

【最后】: 因此其实在使用的时候,我建议修饰基本类型变量的时候使用volatile,修饰引用类型的时候使用AtomicReference.

相关文章
相关标签/搜索