java併發編程實戰:第十六章----Java內存模型

1、什麼是內存模型,爲何要使用它緩存

若是缺乏同步,那麼將會有許多因素使得線程沒法當即甚至永遠看到一個線程的操做結果安全

  • 編譯器把變量保存在本地寄存器而不是內存中
  • 編譯器中生成的指令順序,能夠與源代碼中的順序不一樣
  • 處理器採用亂序或並行的方式來執行指令
  • 保存在處理器本地緩存中的值,對於其餘處理器是不可見

在單線程中,只要程序的最終結果與在嚴格串行環境中執行的結果相同,那麼上述全部操做都是容許的多線程

在多線程中,JVM經過同步操做來找出這些協調操做將在什麼時候發生架構

JMM規定了JVM必須遵循一組最小保證,這組保證規定了對變量的寫入操做在什麼時候將對其餘線程可見app

一、平臺的內存模型函數

每一個處理器都擁有本身的緩存,而且按期地與主內存進行協調,在不一樣的處理器架構中提供了不一樣級別的緩存一致性,即容許不一樣的處理器在任意時刻從同一個存儲位置上看到不一樣的值。JVM經過在適當的位置上插入內存柵欄來屏蔽在JMM與底層平臺內存模型之間的差別。Java程序不須要指定內存柵欄的位置,而只需經過正確地使用同步來找出什麼時候將訪問共享狀態性能

二、重排序線程

  各類使操做延遲或者看似亂序執行的不一樣緣由,均可以歸爲重排序,內存級的重排序會使程序的行爲變得不可預測code

1 Thread one = new Thread(new Runnable() {
2             public void run() {
3                 a = 1;
4                 x = b;
5             }
6         });

上述代碼也會有如下結果:對象

三、Java內存模式簡介

  Java內存模型是經過各類操做來定義的,包括變量的讀/寫操做,監視器的加鎖和釋放操做,以及線程的啓動和合並操做

  JMM爲程序中全部的操做定義了一個偏序關係,稱爲Happens-Before,使在正確同步的程序中不存在數據競爭(缺少Happens-Before關係,那麼JVM能夠對它們任意地重排序)

  • 程序順序規則。若是程序中操做A在操做B以前,那麼在線程中A操做將在B操做以前執行
  • 監視器鎖規則。在監視器鎖上的解鎖操做必須在同一個監視器鎖上的加鎖操做以前執行。(顯式鎖和內置鎖在加鎖和解鎖等操做上有着相同的內存語義) 
  • volatile變量規則。對volatile變量的寫入操做必須在對該變量的讀操做以前執行。(原子變量與volatile變量在讀操做和寫操做上有着相同的語義) 
  • 線程啓動規則。在線程上對Thread.start的調用必須在該線程中執行任何操做以前執行
  • 線程結束規則。線程中的任何操做都必須在其餘線程檢測到該線程已經結束以前執行,或者從Thread.join中成功返回,或者在調用Thread.isAlive時返回false
  • 中斷規則。當一個線程在另外一個線程上調用interrupt時,必須在被中斷線程檢測到interrupt調用以前執行(經過拋出InterruptException,或者調用isInterrupted和interrupted)
  • 終結器規則。對象的構造函數必須在啓動該對象的終結器以前執行完成
  • 傳遞性。若是操做A在操做B以前執行,而且操做B在操做C以前執行,那麼操做A必須在操做C以前執行。

四、藉助同步

」藉助(Piggyback)「現有同步機制的可見性屬性,對某個未被鎖保護的變量的訪問操做進行排序(不但願給對象加鎖,而又想維護它的順序)

 

Happens-Before排序包括:

  • 將一個元素放入一個線程安全容器的操做將在另外一個線程從該容器中得到這個元素的操做以前執行
  • 在CountDownLatch上的倒數操做將在線程從閉鎖上的await方法返回以前執行
  • 釋放Semaphore許可的操做將在從該Semaphore上得到一個許可以前執行
  • Future表示的任務的全部操做將在從Future.get中返回以前執行
  • 向Executor提交一個Runnable或Callable的操做將在任務開始執行以前執行
  • 一個線程到達CyclicBarrier或Exchange的操做將在其餘到達該柵欄或交換點的線程被釋放以前執行。若是CyclicBarrier使用一個柵欄操做,那麼到達柵欄的操做將在柵欄操做以前執行,而柵欄操做又會在線程從柵欄中釋放以前執行

 

2、發佈

形成不正確發佈的真正緣由:"發佈一個共享對象"與"另外一個線程訪問該對象"之間缺乏一種Happens-Before的關係

一、不安全的發佈

  除了不可變對象之外,使用被另外一個線程初始化的對象一般都是不安全的,除非對象的發佈操做是在使用該對象的線程開始使用以前執行

 

 1 public class UnsafeLazyInitialization {
 2     private static Object resource;
 3     
 4     public static Object getInstance(){
 5         if (resource == null){
 6             resource = new Object(); //不安全的發佈
 7         }
 8         return resource;
 9     }
10 }

 

緣由一:線程B看到了線程A發佈了一半的對象

緣由二:即便線程A初始化Resource實例以後再將resource設置爲指向它,線程B仍可能看到對resource的寫入操做將在對Resource各個域的寫入操做以前發生。由於線程B看到的線程A中的操做順序,可能與線程A執行這些操做時的順序並不相同

二、安全發佈

例:BlockingQueue的同步機制保證put在take後執行,A線程放入對象能保證B線程取出時是安全的

  藉助於類庫中如今的同步容器、使用鎖保護共享變量、或都使用共享的volatile類型變量,均可以保證對該變量的讀取和寫入是按照happens-before排序的

  happens-before事實上能夠比安全發佈承諾更強的可見性與排序性

三、安全初始化模式

方式一:加鎖保證可見性與排序性,存在性能問題

 1 public class UnsafeLazyInitialization {
 2     private static Object resource;
 3     
 4     public synchronized static Object getInstance(){
 5         if (resource == null){
 6             resource = new Object(); //不安全的發佈
 7         }
 8         return resource;
 9     }
10 }

 

方式二:提早初始化,可能形成浪費資源

1 public class EagerInitialization {
2     private static Object resource = new Object();
3     public static Object getInstance(){
4         return resource;
5     }
6 }

方式三:延遲初始化,建議

1 public class ResourceFactory {
2     private static class ResourceHolder{
3         public static Object resource = new Object();
4     }
5     public static Object getInstance(){
6         return ResourceHolder.resource;
7     }
8 }

方式四:雙重加鎖機制,注意保證volatile類型,不然出現一致性問題

 1 public class DoubleCheckedLocking {
 2     private static volatile Object resource;
 3     public static Object getInstance(){
 4         if (resource == null){
 5             synchronized (DoubleCheckedLocking.class){
 6                 if (resource == null){
 7                     resource = new Object(); 
 8                 }
 9             }
10         }
11         return resource;
12     }
13 }

 

3、初始化過程當中的安全性

  • 若是能確保初始化過程的安全性,被正確構造的不可變對象在沒有同步的狀況下也能安全地在多個線程之間共享
  • 若是不能確保初始化的安全性,一些本應爲不可變對象的值將會發生改變

初始化安全性只能保證經過final域可達的值從構造過程完成時可見性。對於經過非final域可達的值,或者在構成過程完成後可能改變的值,必須採用同步來確保可見性

相關文章
相關標籤/搜索