亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

java語言

Java程序死鎖問題原理及解決方案

時間:2024-10-04 06:22:54 java語言 我要投稿
  • 相關推薦

Java程序死鎖問題原理及解決方案

  Java 語言通過 synchronized 關鍵字來保證原子性,這是因為每一個 ob ject 都有一個隱含的鎖,這個也稱作監(jiān)視器對象。在進入 synchronized 之前自動獲取此內部鎖,而一旦離開此方式,無論是完成或者中斷都會自動釋放鎖。顯然這是一個獨占鎖,每個鎖請求之間是互斥的。下面是小編為大家?guī)淼腏ava程序死鎖問題原理及解決方案,歡迎閱讀。

Java程序死鎖問題原理及解決方案

  死鎖描述

  死鎖是操作系統(tǒng)層面的一個錯誤,是進程死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操作系統(tǒng)乃至整個并發(fā)程序設計領域最難處理的問題之一。

  事實上,計算機世界有很多事情需要多線程方式去解決,因為這樣才能最大程度上利用資源,才能體現(xiàn)出計算的高效。但是,實際上來說,計算機系統(tǒng)中有很多一次只能由一個進程使用的資源的情況,例如打印機,同時只能有一個進程控制它。在多通道程序設計環(huán)境中,若干進程往往要共享這類資源,而且一個進程所需要的資源還很有可能不止一個。因此,就會出現(xiàn)若干進程競爭有限資源,又推進順序不當,從而構成無限期循環(huán)等待的局面。我們稱這種狀態(tài)為死鎖。簡單一點描述,死鎖是指多個進程循環(huán)等待它方占有的資源而無限期地僵持下去的局面。很顯然,如果沒有外力的作用,那么死鎖涉及到的各個進程都將永遠處于封鎖狀態(tài)。

  系統(tǒng)發(fā)生死鎖現(xiàn)象不僅浪費大量的系統(tǒng)資源,甚至導致整個系統(tǒng)崩潰,帶來災難性后果。所以,對于死鎖問題在理論上和技術上都必須予以高度重視。

  銀行家算法

  一個銀行家如何將一定數目的資金安全地借給若干個客戶,使這些客戶既能借到錢完成要干的事,同時銀行家又能收回全部資金而不至于破產。銀行家就像一個操作系統(tǒng),客戶就像運行的進程,銀行家的資金就是系統(tǒng)的資源。

  銀行家算法需要確保以下四點:

  當一個顧客對資金的最大需求量不超過銀行家現(xiàn)有的資金時就可接納該顧客;

  顧客可以分期貸款, 但貸款的總數不能超過最大需求量;

  當銀行家現(xiàn)有的資金不能滿足顧客尚需的貸款數額時,對顧客的貸款可推遲支付,但總能使顧客在有限的時間里得到貸款;

  當顧客得到所需的全部資金后,一定能在有限的時間里歸還所有的資金。

  清單 1. 銀行家算法實現(xiàn)

  /*一共有5個進程需要請求資源,有3類資源*/

  public class BankDemo {

  // 每個進程所需要的最大資源數

  public static int MAX[][] = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 },

  { 2, 2, 2 }, { 4, 3, 3 } };

  // 系統(tǒng)擁有的初始資源數

  public static int AVAILABLE[] = { 10, 5, 7 };

  // 系統(tǒng)已給每個進程分配的資源數

  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("系統(tǒng)可用的資源數為:/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");

  }

  }

  // 分配資源,并重新更新各種狀態(tài)

  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];

  }

  };

  // 回收資源,并重新更新各種狀態(tài)

  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;

  }

  // 檢查安全性函數

  //所以銀行家算法其核心是:保證銀行家系統(tǒng)的資源數至少不小于一個客戶的所需要的資源數。在安全性檢查函數 chkerr() 上由這個方法來實現(xiàn)

  //這個循環(huán)來進行核心判斷,從而完成了銀行家算法的安全性檢查工作。

  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 系統(tǒng)不安全!!! 本次資源申請不成功!/n");

  return 1;

  }

  }

  System.out.print("/n 經安全性檢查,系統(tǒng)安全,本次分配成功。/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;

  }

  }

  死鎖示例

  死鎖問題是多線程特有的問題,它可以被認為是線程間切換消耗系統(tǒng)性能的一種極端情況。在死鎖時,線程間相互等待資源,而又不釋放自身的資源,導致無窮無盡的等待,其結果是系統(tǒng)任務永遠無法執(zhí)行完成。死鎖問題是在多線程開發(fā)中應該堅決避免和杜絕的問題。

  一般來說,要出現(xiàn)死鎖問題需要滿足以下條件:

  1. 互斥條件:一個資源每次只能被一個線程使用。

  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

  3. 不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪。

  4. 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關系。

  只要破壞死鎖 4 個必要條件之一中的任何一個,死鎖問題就能被解決。

  我們先來看一個示例,前面說過,死鎖是兩個甚至多個線程被永久阻塞時的一種運行局面,這種局面的生成伴隨著至少兩個線程和兩個或者多個資源。代碼清單 2 所示的示例中,我們編寫了一個簡單的程序,它將會引起死鎖發(fā)生,然后我們就會明白如何分析它。

  清單 2. 死鎖示例

  public class ThreadDeadlock {

  public static void main(String[] args) throws InterruptedException {

  ob ject obj1 = new ob ject();

  ob ject obj2 = new ob ject();

  ob ject obj3 = new ob ject();

  Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");

  Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");

  Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");

  t1.start();

  Thread.sleep(5000);

  t2.start();

  Thread.sleep(5000);

  t3.start();

  }

  }

  class SyncThread implements Runnable{

  private ob ject obj1;

  private ob ject obj2;

  public SyncThread(ob ject o1, ob ject o2){

  this.obj1=o1;

  this.obj2=o2;

  }

  @Override

  public void run() {

  String name = Thread.currentThread().getName();

  System.out.println(name + " acquiring lock on "+obj1);

  synchronized (obj1) {

  System.out.println(name + " acquired lock on "+obj1);

  work();

  System.out.println(name + " acquiring lock on "+obj2);

  synchronized (obj2) {

  System.out.println(name + " acquired lock on "+obj2);

  work();

  }

  System.out.println(name + " released lock on "+obj2);

  }

  System.out.println(name + " released lock on "+obj1);

  System.out.println(name + " finished execution.");

  }

  private void work() {

  try {

  Thread.sleep(30000);

  } catch (InterruptedException e) {

  e.printStackTrace();

  }

  }

  }

  在上面的程序中同步線程正完成 Runnable 的接口,它工作的是兩個對象,這兩個對象向對方尋求死鎖而且都在使用同步阻塞。在主函數中,我使用了三個為同步線程運行的線程,而且在其中每個線程中都有一個可共享的資源。這些線程以向第一個對象獲取封鎖這種方式運行。但是當它試著向第二個對象獲取封鎖時,它就會進入等待狀態(tài),因為它已經被另一個線程封鎖住了。這樣,在線程引起死鎖的過程中,就形成了一個依賴于資源的循環(huán)。當我執(zhí)行上面的程序時,就產生了輸出,但是程序卻因為死鎖無法停止。輸出如清單 3 所示。

  清單 3. 清單 2 運行輸出

  t1 acquiring lock on java.lang.ob ject@1dd3812

  t1 acquired lock on java.lang.ob ject@1dd3812

  t2 acquiring lock on java.lang.ob ject@c791b9

  t2 acquired lock on java.lang.ob ject@c791b9

  t3 acquiring lock on java.lang.ob ject@1aa9f99

  t3 acquired lock on java.lang.ob ject@1aa9f99

  t1 acquiring lock on java.lang.ob ject@c791b9

  t2 acquiring lock on java.lang.ob ject@1aa9f99

  在此我們可以清楚地在輸出結果中辨認出死鎖局面,但是在我們實際所用的應用中,發(fā)現(xiàn)死鎖并將它排除是非常難的。

  死鎖情況診斷

  JVM 提供了一些工具可以來幫助診斷死鎖的發(fā)生,如下面程序清單 4 所示,我們實現(xiàn)了一個死鎖,然后嘗試通過 jstack 命令追蹤、分析死鎖發(fā)生。

  清單 4. 死鎖代碼

  import java.util.concurrent.locks.ReentrantLock;

  //下面演示一個簡單的死鎖,兩個線程分別占用 south 鎖和 north 鎖,并同時請求對方占用的鎖,導致死鎖

  public class DeadLock extends Thread{

  protected ob ject myDirect;

  static ReentrantLock south = new ReentrantLock();

  static ReentrantLock north = new ReentrantLock();

  public DeadLock(ob ject obj){

  this.myDirect = obj;

  if(myDirect==south){

  this.setName("south");

  }else{

  this.setName("north");

  }

  }

  @Override

  public void run(){

  if(myDirect==south){

  try{

  north.lockInterruptibly();//占用 north

  try{

  Thread.sleep(500);

  }catch(Exception ex){

  ex.printStackTrace();

  }

  south.lockInterruptibly();

  System.out.println("car to south has passed");

  }catch(InterruptedException ex){

  System.out.println("car to south is killed");

  ex.printStackTrace();

  }finally{

  if(north.isHeldByCurrentThread()){

  north.unlock();

  }

  if(south.isHeldByCurrentThread()){

  south.unlock();

  }

  }

  }

  if(myDirect==north){

  try{

  south.lockInterruptibly();//占用 south

  try{

  Thread.sleep(500);

  }catch(Exception ex){

  ex.printStackTrace();

  }

  north.lockInterruptibly();

  System.out.println("car to north has passed");

  }catch(InterruptedException ex){

  System.out.println("car to north is killed");

  ex.printStackTrace();

  }finally{

  if(north.isHeldByCurrentThread()){

  north.unlock();

  }

  if(south.isHeldByCurrentThread()){

  south.unlock();

  }

  }

  }

  }

  public static void main(String[] args) throws InterruptedException{

  DeadLock car2south = new DeadLock(south);

  DeadLock car2north = new DeadLock(north);

  car2south.start();

  car2north.start();

  }

  }

  jstack 可用于導出 Java 應用程序的線程堆棧,-l 選項用于打印鎖的附加信息。我們運行 jstack 命令,輸出入清單 5 和 6 所示,其中清單 5 里面可以看到線程處于運行狀態(tài),代碼中調用了擁有鎖投票、定時鎖等候和可中斷鎖等候等特性的 ReentrantLock 鎖機制。清單 6 直接打印出出現(xiàn)死鎖情況,報告 north 和 sourth 兩個線程互相等待資源,出現(xiàn)了死鎖。

  清單 5. jstack 運行輸出 1

  [root@facenode4 ~]# jstack -l 31274

  2015-01-29 12:40:27

  Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.45-b01 mixed mode):

  "Attach Listener" daemon prio=10 tid=0x00007f6d3c001000 nid=

  0x7a87 waiting on condition [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "DestroyJavaVM" prio=10 tid=0x00007f6da4006800 nid=

  0x7a2b waiting on condition [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "north" prio=10 tid=0x00007f6da4101800 nid=

  0x7a47 waiting on condition [0x00007f6d8963b000]

  java.lang.Thread.State: WAITING (parking)

  at sun.misc.Unsafe.park(Native Method)

  - parking to wait for <0x000000075903c7c8> (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)

  at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)

  at DeadLock.run(DeadLock.java:50)

  Locked ownable synchronizers:

  - <0x000000075903c798> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  "south" prio=10 tid=0x00007f6da4100000 nid=

  0x7a46 waiting on condition [0x00007f6d8973c000]

  java.lang.Thread.State: WAITING (parking)

  at sun.misc.Unsafe.park(Native Method)

  - parking to wait for <0x000000075903c798> (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)

  at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)

  at DeadLock.run(DeadLock.java:28)

  Locked ownable synchronizers:

  - <0x000000075903c7c8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  "Low Memory Detector" daemon prio=10 tid=0x00007f6da40d2800 nid=

  0x7a44 runnable [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "C2 CompilerThread1" daemon prio=10 tid=0x00007f6da40d0000 nid=

  0x7a43 waiting on condition [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "C2 CompilerThread0" daemon prio=10 tid=0x00007f6da40cd000 nid=

  0x7a42 waiting on condition [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "Signal Dispatcher" daemon prio=10 tid=0x00007f6da40cb000 nid=

  0x7a41 runnable [0x0000000000000000]

  java.lang.Thread.State: RUNNABLE

  Locked ownable synchronizers:

  - None

  "Finalizer" daemon prio=10 tid=0x00007f6da40af000 nid=

  0x7a40 in ob ject.wait() [0x00007f6d89d44000]

  java.lang.Thread.State: WAITING (on ob ject monitor)

  at java.lang.ob ject.wait(Native Method)

  - waiting on <0x0000000759001300> (a java.lang.ref.ReferenceQueue$Lock)

  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)

  - locked <0x0000000759001300> (a java.lang.ref.ReferenceQueue$Lock)

  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)

  at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:171)

  Locked ownable synchronizers:

  - None

  "Reference Handler" daemon prio=10 tid=0x00007f6da40ad000 nid=

  0x7a3f in ob ject.wait() [0x00007f6d89e45000]

  java.lang.Thread.State: WAITING (on ob ject monitor)

  at java.lang.ob ject.wait(Native Method)

  - waiting on <0x00000007590011d8> (a java.lang.ref.Reference$Lock)

  at java.lang.ob ject.wait(ob ject.java:485)

  at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)

  - locked <0x00000007590011d8> (a java.lang.ref.Reference$Lock)

  Locked ownable synchronizers:

  - None

  "VM Thread" prio=10 tid=0x00007f6da40a6000 nid=0x7a3e runnable

  "GC task thread#0 (ParallelGC)" prio=10 tid=0x00007f6da4019800 nid=0x7a2c runnable

  "GC task thread#1 (ParallelGC)" prio=10 tid=0x00007f6da401b000 nid=0x7a2d runnable

  "GC task thread#2 (ParallelGC)" prio=10 tid=0x00007f6da401d000 nid=0x7a2e runnable

  "GC task thread#3 (ParallelGC)" prio=10 tid=0x00007f6da401f000 nid=0x7a2f runnable

  "GC task thread#4 (ParallelGC)" prio=10 tid=0x00007f6da4020800 nid=0x7a30 runnable

  "GC task thread#5 (ParallelGC)" prio=10 tid=0x00007f6da4022800 nid=0x7a31 runnable

  "GC task thread#6 (ParallelGC)" prio=10 tid=0x00007f6da4024800 nid=0x7a32 runnable

  "GC task thread#7 (ParallelGC)" prio=10 tid=0x00007f6da4026000 nid=0x7a33 runnable

  "GC task thread#8 (ParallelGC)" prio=10 tid=0x00007f6da4028000 nid=0x7a34 runnable

  "GC task thread#9 (ParallelGC)" prio=10 tid=0x00007f6da402a000 nid=0x7a35 runnable

  "GC task thread#10 (ParallelGC)" prio=10 tid=0x00007f6da402b800 nid=0x7a36 runnable

  "GC task thread#11 (ParallelGC)" prio=10 tid=0x00007f6da402d800 nid=0x7a37 runnable

  "GC task thread#12 (ParallelGC)" prio=10 tid=0x00007f6da402f800 nid=0x7a38 runnable

  "GC task thread#13 (ParallelGC)" prio=10 tid=0x00007f6da4031000 nid=0x7a39 runnable

  "GC task thread#14 (ParallelGC)" prio=10 tid=0x00007f6da4033000 nid=0x7a3a runnable

  "GC task thread#15 (ParallelGC)" prio=10 tid=0x00007f6da4035000 nid=0x7a3b runnable

  "GC task thread#16 (ParallelGC)" prio=10 tid=0x00007f6da4036800 nid=0x7a3c runnable

  "GC task thread#17 (ParallelGC)" prio=10 tid=0x00007f6da4038800 nid=0x7a3d runnable

  "VM Periodic Task Thread" prio=10 tid=0x00007f6da40dd000 nid=0x7a45 waiting on condition

  JNI global references: 886

  清單 6. jstack 運行輸出片段 2

  Found one Java-level deadlock:

  =============================

  "north":

  waiting for ownable synchronizer 0x000000075903c7c8, (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync),

  which is held by "south"

  "south":

  waiting for ownable synchronizer 0x000000075903c798, (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync),

  which is held by "north"

  Java stack information for the threads listed above:

  ===================================================

  "north":

  at sun.misc.Unsafe.park(Native Method)

  - parking to wait for <0x000000075903c7c8> (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)

  at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)

  at DeadLock.run(DeadLock.java:50)

  "south":

  at sun.misc.Unsafe.park(Native Method)

  - parking to wait for <0x000000075903c798> (

  a java.util.concurrent.locks.ReentrantLock$NonfairSync)

  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)

  at java.util.concurrent.locks.AbstractQueuedSynchronizer.

  acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)

  at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)

  at DeadLock.run(DeadLock.java:28)

  Found 1 deadlock.

  死鎖解決方案

  死鎖是由四個必要條件導致的,所以一般來說,只要破壞這四個必要條件中的一個條件,死鎖情況就應該不會發(fā)生。

  如果想要打破互斥條件,我們需要允許進程同時訪問某些資源,這種方法受制于實際場景,不太容易實現(xiàn)條件;

  打破不可搶占條件,這樣需要允許進程強行從占有者那里奪取某些資源,或者簡單一點理解,占有資源的進程不能再申請占有其他資源,必須釋放手上的資源之后才能發(fā)起申請,這個其實也很難找到適用場景;

  進程在運行前申請得到所有的資源,否則該進程不能進入準備執(zhí)行狀態(tài)。這個方法看似有點用處,但是它的缺點是可能導致資源利用率和進程并發(fā)性降低;

  避免出現(xiàn)資源申請環(huán)路,即對資源事先分類編號,按號分配。這種方式可以有效提高資源的利用率和系統(tǒng)吞吐量,但是增加了系統(tǒng)開銷,增大了進程對資源的占用時間。

  如果我們在死鎖檢查時發(fā)現(xiàn)了死鎖情況,那么就要努力消除死鎖,使系統(tǒng)從死鎖狀態(tài)中恢復過來。消除死鎖的幾種方式:

  1. 最簡單、最常用的方法就是進行系統(tǒng)的重新啟動,不過這種方法代價很大,它意味著在這之前所有的進程已經完成的計算工作都將付之東流,包括參與死鎖的那些進程,以及未參與死鎖的進程;

  2. 撤消進程,剝奪資源。終止參與死鎖的進程,收回它們占有的資源,從而解除死鎖。這時又分兩種情況:一次性撤消參與死鎖的全部進程,剝奪全部資源;或者逐步撤消參與死鎖的進程,逐步收回死鎖進程占有的資源。一般來說,選擇逐步撤消的進程時要按照一定的原則進行,目的是撤消那些代價最小的進程,比如按進程的優(yōu)先級確定進程的代價;考慮進程運行時的代價和與此進程相關的外部作業(yè)的代價等因素;

  3. 進程回退策略,即讓參與死鎖的進程回退到沒有發(fā)生死鎖前某一點處,并由此點處繼續(xù)執(zhí)行,以求再次執(zhí)行時不再發(fā)生死鎖。雖然這是個較理想的辦法,但是操作起來系統(tǒng)開銷極大,要有堆棧這樣的機構記錄進程的每一步變化,以便今后的回退,有時這是無法做到的。

  其實即便是商業(yè)產品,依然會有很多死鎖情況的發(fā)生,例如 MySQL 數據庫,它也經常容易出現(xiàn)死鎖案例。

  MySQL 死鎖情況解決方法

  假設我們用 Show innodb status 檢查引擎狀態(tài)時發(fā)現(xiàn)了死鎖情況,如清單 7 所示。

  清單 7. MySQL 死鎖

  WAITING FOR THIS LOCK TO BE GRANTED:

  RECORD LOCKS space id 0 page no 843102 n bits 600 index `KEY_TSKTASK_MONTIME2` of table

  `dcnet_db/TSK_TASK` trx id 0 677833454 lock_mode X locks rec but not gap waiting

  Record lock, heap no 395 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

  0: len 8; hex 8000000000000425; asc %;; 1: len 8; hex 800012412c66d29c;

  asc A,f ;; 2: len 8; hex 800000000097629c; asc b ;;

  *** WE ROLL BACK TRANSACTION (1)

  我們假設涉事的數據表上面有一個索引,這次的死鎖就是由于兩條記錄同時訪問到了相同的索引造成的。

  我們首先來看看 InnoDB 類型的數據表,只要能夠解決索引問題,就可以解決死鎖問題。MySQL 的 InnoDB 引擎是行級鎖,需要注意的是,這不是對記錄進行鎖定,而是對索引進行鎖定。在 UPDATE、DELETE 操作時,MySQL 不僅鎖定 WHERE 條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的 next-key locking;

  如語句 UPDATE TSK_TASK SET UPDATE_TIME = NOW() WHERE ID > 10000 會鎖定所有主鍵大于等于 1000 的所有記錄,在該語句完成之前,你就不能對主鍵等于 10000 的記錄進行操作;當非簇索引 (non-cluster index) 記錄被鎖定時,相關的簇索引 (cluster index) 記錄也需要被鎖定才能完成相應的操作。

  再分析一下發(fā)生問題的兩條 SQL 語句:

  當“TSK_TASK set STATUS_ID=1064,UPDATE_TIME=now () where STATUS_ID=1061 and MON_TIME

  假設“TSK_TASK set STATUS_ID=1067,UPDATE_TIME=now () where ID in (9921180)”幾乎同時執(zhí)行時,本語句首先鎖定簇索引 (主鍵),由于需要更新 STATUS_ID 的值,所以還需要鎖定 KEY_TSKTASK_MONTIME2 的某些索引記錄。

  這樣第一條語句鎖定了 KEY_TSKTASK_MONTIME2 的記錄,等待主鍵索引,而第二條語句則鎖定了主鍵索引記錄,而等待 KEY_TSKTASK_MONTIME2 的記錄,這樣死鎖就產生了。

  我們通過拆分第一條語句解決了死鎖問題:即先查出符合條件的 ID:select ID from TSK_TASK where STATUS_ID=1061 and MON_TIME < date_sub(now(), INTERVAL 30 minute);然后再更新狀態(tài):TSK_TASK set STATUS_ID=1064 where ID in (….)。

  結束語

  我們發(fā)現(xiàn),死鎖雖然是較早就被發(fā)現(xiàn)的問題,但是很多情況下我們設計的程序里還是經常發(fā)生死鎖情況。我們不能只是分析如何解決死鎖這類問題,還需要具體找出預防死鎖的方法,這樣才能從根本上解決問題?偟膩碚f,還是需要系統(tǒng)架構師、程序員不斷積累經驗,從業(yè)務邏輯設計層面徹底消除死鎖發(fā)生的可能性


【Java程序死鎖問題原理及解決方案】相關文章:

php中死鎖問題剖析10-14

構建Java程序09-04

java程序規(guī)范09-24

java程序編寫10-10

Java原理面試題07-13

如何編寫java程序07-05

Java程序規(guī)范教程09-03

Java動畫程序介紹07-19

JAVA系統(tǒng)程序的運行07-16

Java程序編輯教程10-24