Java 語言通過 synchronized 關鍵字來保證原子性,這是因為每一個 ob ject 都有一個隱含的鎖,這個也稱作監視器對象。在進入 synchronized 之前自動獲取此內部鎖,而一旦離開此方式,無論是完成或者中斷都會自動釋放鎖。顯然這是一個獨占鎖,每個鎖請求之間是互斥的。相對於眾多高級鎖 (Lock/ReadWriteLock 等),synchronized 的代價都比後者要高。但是 synchronzied 的語法比較簡單,而且也比較容易使用和理解。Lock 一旦調用了 lock() 方法獲取到鎖而未正確釋放的話很有可能造成死鎖,所以 Lock 的釋放操作總是跟在 finally 代碼塊裡面,這在代碼結構上也是一次調整和冗余。Lock 的實現已經將硬件資源用到了極致,所以未來可優化的空間不大,除非硬件有了更高的性能,但是 synchronized 只是規范的一種實現,這在不同的平台不同的硬件還有很高的提升空間,未來 Java 鎖上的優化也會主要在這上面。既然 synchronzied 都不可能避免死鎖產生,那麼死鎖情況會是經常容易出現的錯誤,下面具體描述死鎖發生的原因及解決方法。
死鎖描述
死鎖是操作系統層面的一個錯誤,是進程死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操作系統乃至整個並發程序設計領域最難處理的問題之一。
事實上,計算機世界有很多事情需要多線程方式去解決,因為這樣才能最大程度上利用資源,才能體現出計算的高效。但是,實際上來說,計算機系統中有很多一次只能由一個進程使用的資源的情況,例如打印機,同時只能有一個進程控制它。在多通道程序設計環境中,若干進程往往要共享這類資源,而且一個進程所需要的資源還很有可能不止一個。因此,就會出現若干進程競爭有限資源,又推進順序不當,從而構成無限期循環等待的局面。我們稱這種狀態為死鎖。簡單一點描述,死鎖是指多個進程循環等待它方占有的資源而無限期地僵持下去的局面。很顯然,如果沒有外力的作用,那麼死鎖涉及到的各個進程都將永遠處於封鎖狀態。
系統發生死鎖現象不僅浪費大量的系統資源,甚至導致整個系統崩潰,帶來災難性後果。所以,對於死鎖問題在理論上和技術上都必須予以高度重視。
銀行家算法
一個銀行家如何將一定數目的資金安全地借給若干個客戶,使這些客戶既能借到錢完成要干的事,同時銀行家又能收回全部資金而不至於破產。銀行家就像一個操作系統,客戶就像運行的進程,銀行家的資金就是系統的資源。
銀行家算法需要確保以下四點:
當一個顧客對資金的最大需求量不超過銀行家現有的資金時就可接納該顧客;
顧客可以分期貸款, 但貸款的總數不能超過最大需求量;
當銀行家現有的資金不能滿足顧客尚需的貸款數額時,對顧客的貸款可推遲支付,但總能使顧客在有限的時間裡得到貸款;
當顧客得到所需的全部資金後,一定能在有限的時間裡歸還所有的資金。
清單 1. 銀行家算法實現
/*一共有5個進程需要請求資源,有3類資源*/
public class BankDemo {
// 每個進程所需要的最大資源數
public static int MAX[][] = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 },
{ 2, 2, 2 }, { 4, 3, 3 } };
// 系統擁有的初始資源數
public static int AVAILABLE[] = { 10, 5, 7 };
// 系統已給每個進程分配的資源數
public static int ALLOCATION[][] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 },
{ 0, 0, 0 }, { 0, 0, 0 } };
// 每個進程還需要的資源數
public static int NEED[][] = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 },
{ 2, 2, 2 }, { 4, 3, 3 } };
// 每次申請的資源數
public static int Request[] = { 0, 0, 0 };
// 進程數與資源數
public static int M = 5, N = 3;
int FALSE = 0;
int TRUE = 1;
public void showdata() {
int i, j;
System.out.print("系統可用的資源數為:/n");
for (j = 0; j < N; j++) {
System.out.print("資源" + j + ":" + AVAILABLE[j] + " ");
}
System.out.println();
System.out.println("各進程還需要的資源量:");
for (i = 0; i < M; i++) {
System.out.print("進程" + i + ":");
for (j = 0; j < N; j++) {
System.out.print("資源" + j + ":" + NEED[i][j] + " ");
}
System.out.print("/n");
}
System.out.print("各進程已經得到的資源量: /n");
for (i = 0; i < M; i++) {
System.out.print("進程");
System.out.print(i);
for (j = 0; j < N; j++) {
System.out.print("資源" + j + ":" + ALLOCATION[i][j] + " ");
}
System.out.print("/n");
}
}
// 分配資源,並重新更新各種狀態
public void changdata(int k) {
int j;
for (j = 0; j < N; j++) {
AVAILABLE[j] = AVAILABLE[j] - Request[j];
ALLOCATION[k][j] = ALLOCATION[k][j] + Request[j];
NEED[k][j] = NEED[k][j] - Request[j];
}
};
// 回收資源,並重新更新各種狀態
public void rstordata(int k) {
int j;
for (j = 0; j < N; j++) {
AVAILABLE[j] = AVAILABLE[j] + Request[j];
ALLOCATION[k][j] = ALLOCATION[k][j] - Request[j];
NEED[k][j] = NEED[k][j] + Request[j];
}
};
// 釋放資源
public void free(int k) {
for (int j = 0; j < N; j++) {
AVAILABLE[j] = AVAILABLE[j] + ALLOCATION[k][j];
System.out.print("釋放" + k + "號進程的" + j + "資源!/n");
}
}
public int check0(int k) {
int j, n = 0;
for (j = 0; j < N; j++) {
if (NEED[k][j] == 0)
n++;
}
if (n == 3)
return 1;
else
return 0;
}
// 檢查安全性函數
//所以銀行家算法其核心是:保證銀行家系統的資源數至少不小於一個客戶的所需要的資源數。在安全性檢查函數 chkerr() 上由這個方法來實現
//這個循環來進行核心判斷,從而完成了銀行家算法的安全性檢查工作。
public int chkerr(int s) {
int WORK;
int FINISH[] = new int[M], temp[] = new int[M];// 保存臨時的安全進程序列
int i, j, k = 0;
for (i = 0; i < M; i++)
FINISH[i] = FALSE;
for (j = 0; j < N; j++) {
WORK = AVAILABLE[j]; // 第 j 個資源可用數
i = s;
// 判斷第 i 個進程是否滿足條件
while (i < M) {
if (FINISH[i] == FALSE && NEED[i][j] <= WORK) {
WORK = WORK + ALLOCATION[i][j];
FINISH[i] = TRUE;
temp[k] = i;
k++;
i = 0;
} else {
i++;
}
}
for (i = 0; i < M; i++)
if (FINISH[i] == FALSE) {
System.out.print("/n 系統不安全!!! 本次資源申請不成功!/n");
return 1;
}
}
System.out.print("/n 經安全性檢查,系統安全,本次分配成功。/n");
System.out.print("本次安全序列:");
for (i = 0; i < M - 1; i++) {
System.out.print("進程" + temp[i] + "->");
}
System.out.print("進程" + temp[M - 1]);
System.out.println("/n");
return 0;
}
}
死鎖示例
死鎖問題是多線程特有的問題,它可以被認為是線程間切換消耗系統性能的一種極端情況。在死鎖時,線程間相互等待資源,而又不釋放自身的資源,導致無窮無盡的等待,其結果是系統任務永遠無法執行完成。死鎖問題是在多線程開發中應該堅決避免和杜絕的問題。
一般來說,要出現死鎖問題需要滿足以下條件:
1. 互斥條件:一個資源每次只能被一個線程使用。
2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
3. 不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪。
4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
只