基于ReentrantLock发生死锁的解决方案
概念
死鎖
是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。 由于資源占用是互斥的,當某個進程提出申請資源后,使得有關進程在無外力協助下,永遠分配不到必需的資源而無法繼續運行,這就產生了一種特殊現象:死鎖。”
活鎖(英文 livelock)
指事物1可以使用資源,但它讓其他事物先使用資源;事物2可以使用資源,但它也讓其他事物先使用資源,于是兩者一直謙讓,都無法使用資源。
避免活鎖的簡單方法是采用先來先服務的策略。當多個事務請求封鎖同一數據對象時,封鎖子系統按請求封鎖的先后次序對事務排隊,數據對象上的鎖一旦釋放就批準申請隊列中第一個事務獲得鎖。
示例一
使用tryLock()方法來防止多線程死鎖。
tryLock()方法:嘗試獲取一把鎖,如果獲取成功返回true,如果還拿不到鎖,就返回false。
public class DeadLock1 {private static Lock lock1 = new ReentrantLock();private static Lock lock2 = new ReentrantLock();public static void deathLock() {new Thread() {@Overridepublic void run() {while (true) {if (lock1.tryLock()) {try {//如果獲取成功則執行業務邏輯,如果獲取失敗,則釋放lock1的鎖,自旋重新嘗試獲得鎖if (lock2.tryLock()) {try {System.out.println("Thread1:已成功獲取 lock1 and lock2 ...");break;} finally {lock2.unlock();}}} finally {lock1.unlock();}}System.out.println("Thread1:獲取鎖失敗,重新獲取---");try {//防止發生活鎖TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}}}.start();new Thread() {@Overridepublic void run() {while (true) {if (lock2.tryLock()) {try {//如果獲取成功則執行業務邏輯,如果獲取失敗,則釋放lock2的鎖,自旋重新嘗試獲得鎖if (lock1.tryLock()) {try {System.out.println("Thread2:已成功獲取 lock2 and lock1 ...");break;} finally {lock1.unlock();}}} finally {lock2.unlock();}}System.out.println("Thread2:獲取鎖失敗,重新獲取---");try {//防止發生活鎖TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}}}.start();}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 5; i++) {deathLock();}} }該示例啟動兩個線程。線程1首先獲取lock1的鎖,然后再獲取lock2的鎖;線程2首先獲取lock2的鎖,然后再獲取lock1的鎖。這樣如果這時線程1獲得了lock1的鎖,同時線程2獲得lock2的鎖,然后線程1嘗試去獲得lock2的鎖,線程2嘗試獲得線程1的鎖,就會造成死鎖。
我們這里使用tryLock來獲取兩個鎖,如果一個線程不能同時獲取兩把鎖,那么就回退并自旋重新嘗試(使用while循環)。再使用TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));隨機休眠一段時間,從而降低發生活鎖的可能性。如果處理成功,則使用break跳出循環。
示例二
使用tryLock(long timeout, TimeUnit unit) 方法來防止多線程死鎖。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class DeadLock2 {private static ReentrantLock lock1 = new ReentrantLock();private static ReentrantLock lock2 = new ReentrantLock();public static void deathLock() {new Thread() {@Overridepublic void run() {while (true) {try {if (lock1.tryLock(10, TimeUnit.MILLISECONDS)) {try {//如果獲取成功則執行業務邏輯,如果獲取失敗,則釋放lock1的鎖,自旋重新嘗試獲得鎖if (lock2.tryLock(10, TimeUnit.MILLISECONDS)) {System.out.println("Thread1:已成功獲取 lock1 and lock2 ...");break;}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock2.isHeldByCurrentThread()){lock2.unlock();}}}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock1.isHeldByCurrentThread()){lock1.unlock();}}System.out.println("Thread1:獲取鎖失敗,重新獲取---");try {TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}}}.start();new Thread() {@Overridepublic void run() {while (true) {try {if (lock2.tryLock(10, TimeUnit.MILLISECONDS)) {try {//如果獲取成功則執行業務邏輯,如果獲取失敗,則釋放lock1的鎖,自旋重新嘗試獲得鎖if (lock1.tryLock(10, TimeUnit.MILLISECONDS)) {System.out.println("Thread2:已成功獲取 lock2 and lock1 ...");break;}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock1.isHeldByCurrentThread()){lock1.unlock();}}}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock2.isHeldByCurrentThread()){lock2.unlock();}}System.out.println("Thread2:獲取鎖失敗,重新獲取---");try {TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}}}.start();}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 5; i++) {deathLock();}} }該示例同示例一。我們這里使用tryLock(long time, TimeUnit unit)來獲取兩個鎖,如果一個線程不能同時獲取兩把鎖,那么就回退并自旋重新嘗試(使用while循環)。在使用TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));隨機休眠一段時間,從而降低發生活鎖的可能性。如果處理成功,則使用break跳出循環。
示例三
使用lockInterruptibly()獲得鎖,如果發生死鎖,調用線程interrupt來消除死鎖。
ReentrantLock.lockInterruptibly允許在等待時由其它線程調用等待線程的Thread.interrupt方法來中斷等待線程的等待而直接返回,這時不用獲取鎖,而會拋出一個InterruptedException。而ReentrantLock.lock方法不允許Thread.interrupt中斷,即使檢測到Thread.isInterrupted,一樣會繼續嘗試獲取鎖,失敗則繼續休眠。只是在最后獲取鎖成功后再把當前線程置為interrupted狀態。
public class DeadLock3 {private static Lock lock1 = new ReentrantLock();private static Lock lock2 = new ReentrantLock();public static void deathLock() {new Thread() {@Overridepublic void run() {try {lock1.lockInterruptibly();try {TimeUnit.SECONDS.sleep(1);lock2.lockInterruptibly();System.out.println("thread 1 ...");} catch (InterruptedException e) {e.printStackTrace();} finally {lock2.unlock();}} catch (InterruptedException e1) {e1.printStackTrace();} finally {lock1.unlock();}}}.start();new Thread() {@Overridepublic void run() {try {lock2.lockInterruptibly();try {TimeUnit.SECONDS.sleep(1);lock1.lockInterruptibly();System.out.println("thread 1 ...");} catch (InterruptedException e) {e.printStackTrace();} finally {lock1.unlock();}} catch (InterruptedException e1) {e1.printStackTrace();} finally {lock2.unlock();}}}.start();}public static void main(String[] args) throws InterruptedException {deathLock();TimeUnit.SECONDS.sleep(2);checkDeadLock();}//基于JMX獲取線程信息public static void checkDeadLock() {//獲取Thread的MBeanThreadMXBean mbean = ManagementFactory.getThreadMXBean();//查找發生死鎖的線程,返回線程id的數組long[] deadLockThreadIds = mbean.findDeadlockedThreads();System.out.println("---" + deadLockThreadIds);if (deadLockThreadIds != null) {//獲取發生死鎖的線程信息ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockThreadIds);//獲取JVM中所有的線程信息Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();for (Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {for (int i = 0; i < threadInfos.length; i++) {Thread t = entry.getKey();if (t.getId() == threadInfos[i].getThreadId()) {//中斷發生死鎖的線程t.interrupt();//打印堆棧信息 // for (StackTraceElement ste : entry.getValue()) {// // System.err.println("t" + ste.toString().trim());// }}}}}} }我們這里使用lockInterruptibly()方法來獲取鎖,我們這里使用線程1獲取lock1 休眠1秒,嘻嘻按錯2獲取lock2 休眠1秒,1秒過后,然后線程1再獲取lock2,線程2再去獲得lock1就會發生死鎖。這是我們又執行了checkDeadLock()方法,來檢查JVM中是否有死鎖,如果有死鎖,則把發生死鎖的線程執行interrupt()方法,使該線程響應中斷,從而避免發生死鎖。(實際應用中,檢查死鎖可以單獨開啟一個daemon線程,每間隔一段時間檢查一下是否發生死鎖,如果有則預警、記錄日志、或中斷該線程避免死鎖)
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結
以上是生活随笔為你收集整理的基于ReentrantLock发生死锁的解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最简单的 java 防反编译技巧
- 下一篇: ExecutorCompletionSe