深刻研究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.

相關文章
相關標籤/搜索