Volatile

Volatile

標籤(空格分隔): 進程/線程 操做系統java


Java虛擬機提供的輕量級的同步機制c++

1. 保證可見性

不一樣的線程進入共享內存中讀取數據以後, 在各自的工做空間對數據一通操做, 而後寫入共享內存中, 這個時候由於共享內存的數據改變, 這個時候會通知其餘讀取該共享變量的線程, 通知該數據已經改變.函數

/**
 * 1. 驗證Volatile的可見性.
 * 1.1 加入number=0; number沒有添加Volatile關鍵字修飾---沒有可見性.
 * 1.2 在該線程對其私有虛擬機棧中棧幀中的備份number操做以後, 不會將數據覆蓋到主內存當中.
 * 2. 加入Volatile以後
 * 2.1 在線程對其備份修改完畢以後, 會將數據覆蓋到主內存當中.
 */
public class VolatileDemo {
    public static void main(String[] args) {
        val myData = new MyData();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t updated number value: " + myData.number);
        }, "aaa").start();

        while (myData.number == 0) {

        }

        System.out.println("任務結束, number: " + myData.number);

    }
}
class MyData {
//    volatile  int number = 0;
    int number = 0;

    public void addTo60() {
        this.number = 60;
    }

    public void addPlusPlus() {
        this.number++;
    }
}

2. 不保證原子性

/**
 * Volatile 不保證原子性
 *
 * 在多個線程對棧幀中的number修改完畢以後, 在A線程立刻開始寫入主內存的時候被打斷了, 這個時候B線程把本身的計算結果寫入了
 * 這個時候, 就會產生計算結果被覆蓋的狀況. 而後永遠都計算不出來正確的值.  這個就是Volatile的原子性問題.
 */
public class VolatileDemo1 {
    public static void main(String[] args) throws InterruptedException {
        MyData myData = new MyData();


        for (int i = 0; i < 20; i++) {

            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }
            }, String.valueOf(i)).start();

        }

        /* main線程   和  後臺GC線程 */
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(myData.number);
        System.out.println("剩餘線程數量"+ Thread.activeCount());
    }
}
class MyData {

    /* 經過 java.util.concurrent.atomic 包去 實現原子性操做. 自旋鎖 
    * 能夠看到AtomicInteger構造函數發現 最後存儲的方式 private volatile int value;
    * */
    AtomicInteger atomicInteger = new AtomicInteger(1);

    public int addAtomic(){
        return atomicInteger.getAndIncrement();
    }
}

3. 禁止指令重排

指令隊列在CPU執行時不是串行的, 當某條指令執行時消耗較多時間時, CPU資源足夠時並不會在此無心義的等待, 而是開啓下一個指令. 開啓下一條指令是有條件的, 即上一條指令和下一條指令不存在相關性. 例以下面這個例子:this

a /= 2;   // 指令A
a /= 2;   // 指令B
c++;      // 指令C

這裏的指令B是依賴於指令A的執行結果的, 在A處於執行階段時, B會被阻塞, 直到A執行完成. 而指令C與A/B均沒有依賴關係, 因此在A執行或者B執行的過程當中, C會同時被執行, 那麼C有可能在A+B的執行過程當中就執行完畢了, 這樣指令隊列的實際執行順序就是 C->A->B 或者 A->C->B.atom

可能出現指令重排致使問題的代碼操作系統

public void method1()  {
    a = 1;                  // 語句1
    flag = true;            // 語句2
}

public void method2() {
    if (flag) {
        a = a + 5;
    }
}

工做區域和主內存出現的同步延遲現象致使的可見性問題可使用synchronize或volatile解決. 他們均可以使一個線程修改後的變量當即對其餘線程可見.線程

運算指令

運算由運算器單元(ALU)實現,指令包括算術運算指令、邏輯運算指令和移位指令。
算術運算指令實現加減乘除(+-*/)等基本的算術運算;邏輯運算指令實現與或非(&|~)等基本的邏輯運算;移位指令實現二進制比特位(bit)的左右移(<<>>)運算。code

控制指令

除了作計算外,CPU還要實現循環。循環是由跳轉指令實現的,跳回去執行就是循環。循環在必定條件下跳出,不然就成死循環了,條件跳轉指令能完成這個功能。條件跳轉指令在必定條件下實現跳轉,它能實現分支功能。跳轉指令也稱爲控制指令。控制由CPU控制器單元實現。隊列

數據傳送指令

運算和控制指令的操做數從哪裏來的呢?操做數都放在存儲器中。在x86 IA中,運算指令的操做數既能夠是寄存器,也能夠是存儲器;而在其餘RISCIA例如MIPS中,運算指令的操做數只能是寄存器,所以須要先使用加載(load)指令將存儲器中的數據導入到寄存器中,運算完成後,再用存儲(store)指令將寄存器中的運算結果數據導出到存儲器中。這類指令就是數據傳送指令。進程


可見性的實現就是 數據傳送指令中的先使用加載(load)指令將存儲器中的數據導入到寄存器中,運算完成後,再用存儲(store)指令將寄存器中的運算結果數據導出到存儲器中。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息