java Volatile總結

Java 裏面解決併發問題的有:加鎖(SynChronized,lock),利用管道進行線程間通信, 阻塞隊列(BlockingQueue),使用Java大牛Doug Lea寫的Executors/Executor/ExecutorService/ThreadPoolExecutor,使用計數器CountDownLatch來控制

介紹volatile

在Java線程併發處理中,Java引入了Volatile變量。這次主要說說這個變量

在jdk1.2之前 Java內存模型的實現是總主內存(即共享內存)讀取變量,不需要特別注意,但是隨着jvm的發展和成熟,當前內存模型下,線程是將變量存在本地內存中(比如機器的寄存器),而不是直接從主存中進行讀寫,而這就造成了主存和寄存器的數據不統一。

要解決這個問題,Java給出了Volatile變量,用Volatitle修飾的變量,在每次線程訪問時,都強迫從共享內存中讀取數據成員變量,當成員變量發生變化時,強迫線程變化值寫到共享內存中,這樣,在任何時刻,兩個不同線程總是看到弄個成員變量的同一個值。

Volatile 是一宗削弱的同步機制,在訪問Volatile 修飾的變量時候不會執行加鎖操作,也就不會執行線程阻塞,因此volatilei變量是一種比synchronized關鍵字更輕量級的同步機制。

使用

Java內存包含以下三種情況

  1. 原子性
  2. 有序性
  3. 可見性

Volatile兩層語義

  • 保證不同線程對這個變量進行的操作的可見性,即一個線程修改某個變量的值,這新值對其他線程是立即可見的
  • 禁止進行指令重排

那麼volatile能保證原子性嗎?

下面的例子

public class VolatileTest {


    public static void main(String args[]) {
        for (int i = 0; i < 10000; i++) {
            new Thread(new VoRunnable()).start();
        }
    }

    static class VoRunnable implements Runnable {
        static volatile int ic = 0;

        @Override
        public void run() {
            ic++;
            System.out.println("ic = "+ic);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運行結果:有一點概率,但是如果把線程開的越多,則結果會越明顯,最後的值不一定能加到10000,所以表明Volatile是不能保證原子性的。

原理:自增操作不是原子性的,而且Volatile也無法保證對變量的操作都是原子性的。

如要保證原子性可以使用synchronized、lock、AtomicInteger等。

那麼volatile能保證有序性嗎?

例子

public class VolatileTest {

    static String sbu  = null;
    static volatile boolean inited = true;

    public static void main(String args[]) {
        for (int i = 0; i < 101; i++) {
            new Thread(new VoRunnable2(i)).start();
        }
        new Thread(new VoRunnable()).start();
    }

    static class VoRunnable2 implements Runnable{

        volatile int ic = 0;

        VoRunnable2(int i){
            this.ic = i;
        }

        @Override
        public void run() {
            System.out.println("ic = "+ ic);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ic == 100){
                sbu = new String("123456");
                inited = false;
            }
        }
    }

    static class VoRunnable implements Runnable {
        static volatile int ic = 0;

        @Override
        public void run() {
            while (inited){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(sbu == null){
                System.out.println(" sbu = kong" );
            }else {
                System.out.println(" sbu = " + sbu);
            }
        }
    }
}

上述程序運行結果表明:只要Volatile對inited,進行修飾,就不會出現這種問題。

總結

  1. 它確保指令重排序時不會把其後面的指令排到內存 屏障之前的位置,也不會把前面的指令排到內存屏障的後面; 即在執行到內存屏障這句指令時,在它前面的操作已經全部 完成
  2. 它會強制將對緩存的修改操作立即寫入主存
  3. 如果是寫操作,它會導致其他 CPU 中對應的緩存行 無效。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章