Java 內存模型

1. 內存模型概念多線程

(1)內存模型(Java Memory Model)和內存結構(堆棧那些)不是一個層面的概念,JMM 定義了一套在多線程讀寫共享數據(成員變量,靜態變量等,而不是局部變量這種線程私有的)時,對數據的可見性、有序性、和原子性的規則和保障。併發

(2)JMM規定了全部的變量都存儲在主內存(虛擬機內存的一部分,但能夠和操做系統的類比)中;ide

每條線程還有本身的工做內存,線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做(讀取,賦值)都必須在工做內存中進行,而不能直接讀寫主內存中的變量;優化

不一樣的線程之間也沒法直接訪問對方的工做內存中的變量,線程間變量的值傳遞均須要經過主內存來完成。操作系統

(3)若是非要把主內存工做內存,和內存結構的堆棧方法區對應,那麼主內存應該對應堆中的對象的實例數據部分,而工做內存對應棧。線程

 

2. 原子性對象

要執行就執行完,不能執行一半blog

違背原子性例子:兩個線程對一個靜態變量 i=0 執行 i++ 和 i--(每一個對應四條JVM字節碼指令),可能致使結果不是0內存

解決:用synchronized加鎖,加鎖位置應儘可能減小代碼獲取釋放鎖的次數編譯器

synchronized( 對象 ) {
    要做爲原子操做代碼
}

 

3. 可見性

指一個對象能看到或訪問另外一個對象的能力

違背可見性的例子:t線程看不到主線程run變量的改變

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{while(run){// ....        }
    });
    t.start();
    Thread.sleep(1000);
    run = false; // 線程t不會如預想的停下來}

解決:volatile(易變關鍵字),它能夠用來修飾成員變量和靜態成員變量,線程操做 volatile 變量都是直接操做主存

所以上面的run被修改後會存到主內存,t線程訪問主內存內容會看到改變

 

4. 有序性

代碼按順序執行

違背有序性的例子:因爲即時編譯器在運行時會有指令重排的優化,多線程狀況下可能出現非預期結果

解決:volatile 修飾的變量,能夠禁用指令重排

所以volatile能夠保證可見性和有序性,不能保證原子性,但屬於輕量級併發控制;而synchronized能夠保證三者,但更重量級

 

5. CAS

CAS 即 Compare and Swap ,它的實現用的是樂觀鎖的思想

// 須要不斷嘗試while(true) {int 舊值 = 共享變量 ; // 好比拿到了當前值 0int 結果 = 舊值 + 1; // 在舊值 0 的基礎上增長 1 ,正確結果是 1if( compareAndSwap ( 舊值, 結果 )) {// 成功,退出循環    }
}
  • 獲取共享變量時,爲了保證該變量的可見性,須要使用 volatile 修飾
  • 結合 CAS 配合 volatile 能夠實現無鎖併發,適用於競爭不激烈、多核 CPU 的場景下
  • 由於沒有使用 synchronized(悲觀鎖),因此線程不會陷入阻塞,這是效率提高的因素之一
  • 但若是競爭激烈,能夠想到重試必然頻繁發生,反而效率會受影響
  • CAS 底層依賴於一個 Unsafe 類來直接調用操做系統底層的 CAS 指令
  • 原子操做類例如:AtomicInteger、AtomicBoolean等,它們底層就是採用 CAS 技術 + volatile 來實現的
相關文章
相關標籤/搜索