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