1. 程式人生 > >併發程式設計藝術筆記

併發程式設計藝術筆記

減少上下文切換   (Lmbench3 時長、vmstat 次數)
  1.無鎖併發   任務分段
  2.CAS
  3.使用最少執行緒     任務少,執行緒多,大多執行緒處於等待狀態
  4.協程   單執行緒實現多工
  
避免死鎖常見方法:
    1.避免一個執行緒同時獲取多個鎖。
    2.避免一個執行緒內在鎖內佔用多個資源,儘量保證每個鎖佔用一個
    3.嘗試使用定時鎖lock.tryLock
    4.對於資料庫鎖,加鎖解鎖必須在一個連線內
    
資源限制(將序列執行的部分變成併發執行,資源不足會變慢)
    硬體:頻寬,硬碟,使用叢集  資料id%機器數
    軟體:資料庫連線數,socket數   使用資源複用
   根據不同的資源限制調整程式的併發度
   
synchronized實現同步的基礎:
    1.普通同步方法,鎖例項物件
    2.靜態            Class物件
    3.同步方法塊,鎖括號中物件 synchronized(物件)
    在jvm中使用Monitor物件來實現,方法同步(也可用以下方法實現這裡用另外
    的一種方法)、
    程式碼塊同步(monitorenter,monitorexit)

執行緒通訊:1.共享記憶體(隱式通訊)  2.訊息傳遞(之間傳送訊息,顯式)
            顯式同步,需要指定互斥塊     隱式同步傳送必須在接受訊息前

java使用共享記憶體模型

在沒有  資料依賴性      的情況下,會進行指令重排
      (讀寫、寫讀、寫寫)

   當存在控制相關性的時候  也會進行指令重排
   if(flag){
      a = i*i;
   }
   先執行temp=i*i;(存入重排序緩衝)  然後判斷flag,為真執行a=temp;
 單執行緒中對控制依賴的操作重排序不會改變執行結果,多執行緒可能會!!

順序一致性記憶體模型
    1.一個執行緒所有操作必須按照程式的順序進行
    2.所有執行緒都能看到單一操作執行順序,每個操作原子,且立刻對其他執行緒可見。

JMM(java memory model)通過同步程式來達到順序一致性效果,儘可能為編譯器和處理器優化便利。

JMM 不保證
    1.單執行緒內操作按程式順序執行
    2.所有執行緒都能看到一致的操作執行順序
    3.對64bit的long double 寫操作的無原子性 jdk5以後讀為原子操作
                         

volatile的記憶體語義
        (可見性)
            對被該修飾的變數,單個讀和寫可看做是使用同一個鎖對單個讀寫進行同步
        如果是多個volatile操作或者複合操作(volatile++)整體上不具有原子性
        (單個volatile變數的讀寫的原子性)
            讀,,寫,,寫讀,讀寫不具有原子性
        (部分有序性)
    操作1 普通讀寫   操作2 volatile寫禁止,,其他的允許
        當第二個操作是volatile寫的時候,禁止重排序
        當第一個操作是volatile讀,禁止重排序
        第一位volatile寫,第二為volatile讀寫不允許排序     普通讀寫可以

-------------------------------------------------------------------------------------------
普通讀寫                                                volatile讀
StoreStore屏障  禁止普通寫與下一步重排                 LoadLoad屏障    禁止後面的
volatile寫                                               LoadStore屏障   讀寫和volatile重排
StoreLoad屏障 禁止上一步與後面可能的volatile讀寫重排     普通讀寫
----------------------------------------------------------------------------------------------
LoadLoad      確保Load1資料裝載先於Load2以及所有後續裝載指令
LoadStore     確保Load1資料裝載先於Store一局所有後續儲存指令
StoreStore      確保Store1資料對其他處理器可見(重新整理到記憶體)先於Store2以及所有後續儲存指令
StoreLoad      確保Store1資料對其他處理器可見(重新整理到記憶體)先於Load2以及所有後續裝載指令的裝載。


鎖的記憶體語義    除了讓 臨界區互斥執行  釋放鎖的執行緒向獲取同一個鎖的執行緒傳送訊息
    1.執行緒A釋放一個鎖,實質上是A向接下來獲取該鎖的某執行緒傳送訊息
    2.執行緒B獲取一個鎖,實質上是B獲取了之前的某個執行緒發出的訊息
    3。A釋放,B獲取,即A通過主記憶體向B傳送訊息

AQS(AbstractQueueSynchronizer)


final域的重排序規則
以下不能重排序
    1.建構函式內對一個final域的寫入,與隨後把該物件的引用賦值給一個引用變數
    2.初次讀一個final域的物件的引用,與隨後初次讀這個final域   
 final域的寫重排序規則
    1.JMM禁止編譯器把final域的寫重排到建構函式之外
    2.編譯器會在final寫之後,建構函式return之前,插入一個StoreStore屏障禁止把final寫重排到建構函式之
   外
 final域的讀重排序規則
    1.一個執行緒中初次讀物件引用與初次讀對該物件包含的final域,JMM禁止處理器重排序這兩個操作(僅對CPU)
  會在final域讀操作之前插入一個LoadLoad  兩個操作之間存在間接依賴關係,禁止少數CPU重排序這兩個操作。

DoubleCheck和延遲初始化    減少建立類的開銷增加了耗時
    DoubleCheck
        利用volatile                         禁止 初始化物件與設定指向instance的空間重排序
        
    基於類初始化                               允許重排序,但是不被其他執行緒看到
        private static class InstanceHolder{
            public static Resource re = new Resource();    //會使用 Class物件初始化鎖 和 state欄位
        }                                                 //其中狀態有noInitialization  initializing
        public static Resource getResource(){            //initialized   三個狀態 ,其他檢測到欄位wei
            return InstanceHolder.re;                 //第二個,就釋放鎖,掛起等待喚醒
        }
  
Daemon執行緒 在start之前設定  其執行緒中的finally塊不一定執行


第4章
Thread.interrupted()對執行緒的中斷方法進行復位

安全執行緒終止應使用 中斷   或者    標識位

   !!!僅僅通過flag     然後迴圈等待,會 難以保持及時性,難以 降低開銷

通過一個object的wait(會釋放鎖)  notify  ,以及flag  迴圈等待  來對兩個執行緒之間傳遞訊息
ThreadA ThreadB  需要都獲得 object的物件鎖
    notifyAll() 會把所有等待執行緒全移動到同步佇列  被移動的執行緒狀態由Waiting變為Blocked
    
    static boolean flag = true;
    static Object lock = new Object();
ThreadA    
    synchronized(lock){
        while(flag){
        lock.wait();
        }
        處理邏輯
    }
ThreadB
   synchronized(lock){
        flag = false;
        lock.notifyAll();
    }

thread.join()  join()會在thread死亡後返回當前執行緒

ThreadLocal 可以用於計算方法時間   set(obj)   get(obj)  以一個ThreadLocal為鍵,任意物件為值的儲存結構


執行緒等待超時模式 
    public synchronized Object get(long mills) throws InterruptedException{   //對當前物件加鎖
        long future = System.currentTimeMills()+mills;  //   等待終止時間
        long remaining = mills;                         //   剩餘等待時間
        while((result==null)&&remaining>0){             //   沒有得到結果 且 仍存在等待時間 迴圈等待
            wait(remaining);                            //等待
            remaining = future - System.currentTimeMills();   //  等待後 剩餘時間
        }
        return result;   //返回結果
    }