併發程式設計藝術筆記
減少上下文切換 (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; //返回結果
}