JAVA多线程死锁解决方案
weijiang2
9年前
<h2><strong>什么是java多线程中的死锁?</strong></h2> <p>通过一个简单的小故事你可以更容易地理解它:<br> 桌上放着一副刀叉,两份牛排,小强和小明只有同时拿到刀和叉才能吃牛排。一开始,两人都去竞争餐具,小强拿到了刀,小明拿到了叉,然后小强拿着刀等待小明占有的叉,小明拿着叉等待小强占有的刀,如此他们陷入了彼此等待死锁,谁也得不到还差的餐具,吃不了牛排。</p> <p>比较概括性的解释:<strong>多个线程同时被阻塞,其中一个或者全部都在等待某个资源被释放。由于线程被无限地阻塞,因此程序不可能正常终止</strong>。</p> <h2><strong>上锁同步模拟死锁:</strong></h2> <pre> <code>import java.text.SimpleDateFormat; import java.util.Date; public class DeadlockTest { public static String th1 = "Dao"; public static String th2 = "Cha"; public static SimpleDateFormat sdf = new SimpleDateFormat("yyy/MM/dd hh:mm:ss"); public static void main(String[] args) { XiaoQiang1 XiaoQiang1 = new XiaoQiang1(); new Thread(XiaoQiang1).start(); XiaoMing1 XiaoMing1 = new XiaoMing1(); new Thread(XiaoMing1).start(); } } class XiaoQiang1 implements Runnable{ public void run() { try { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoQiang1 starts asking for Dao and Cha......"); while(true){ synchronized (DeadlockTest.th1) { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoQiang1 gets Dao!"); Thread.sleep(3000); // 此处休眠不释放锁(模拟对资源Dao的占用); synchronized (DeadlockTest.th2) { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoQiang1 gets Cha!"); Thread.sleep(10 * 1000); // 线程休眠不释放锁,让线程占用对象锁的时间久一点; } } } } catch (Exception e) { e.printStackTrace(); } } } class XiaoMing1 implements Runnable{ public void run() { try { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoMing1 starts asking for Dao and Cha......"); while(true){ synchronized (DeadlockTest.th2) { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoMing1 gets Cha!"); Thread.sleep(3000); // / 此处休眠不释放锁(模拟对资源Cha的占用); synchronized (DeadlockTest.th1) { System.out.println(DeadlockTest.sdf.format(new Date()).toString() + " XiaoMing1 gets Dao!"); Thread.sleep(10 * 1000); // 线程休眠不释放锁,让线程占用对象锁的时间久一点; } } } } catch (Exception e) { e.printStackTrace(); } } }</code></pre> <p>编译运行之后的结果:</p> <blockquote> <p>2016/04/29 12:43:54 XiaoQiang1 starts asking for Dao and Cha......<br> 2016/04/29 12:43:54 XiaoMing1 starts asking for Dao and Cha......<br> 2016/04/29 12:43:54 XiaoQiang1 gets Dao!<br> 2016/04/29 12:43:54 XiaoMing1 gets Cha!<br> (陷入死锁......)</p> </blockquote> <p>此处把小强和小明看做两个线程,刀叉是资源。总结<strong>死锁的四个特点(或者说产生死锁的四个必要条件)</strong>:<br> 1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。<br> 2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。<br> 3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。<br> 4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。</p> <h2><strong>如何解决死锁问题?</strong></h2> <p>只要打破产生死锁的四个必要条件之一,死锁就会消失。</p> <p>这个实际问题的解决方法:小强和小明在获取完整餐具失败或者用完之后都先释放餐具的使用权一段时间(通过信号量来控制),这样就会存在资源使用权空闲时间段可以供另外一个人去获取餐具,不至于陷入无限循环等待的窘境。</p> <h2><strong>Semaphore类介绍</strong></h2> <p>这就要用到java中的Semaphore类(一个计数信号量。从概念上讲,信号量维护了一个许可集)。Semaphore不使用实际的许可对象,只对可用许可的号码进行计数,并采取相应的行动。所谓的许可数目就是用于限制可以访问某些资源(物理或逻辑的)的线程数目</p> <p>信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。</p> <ol> <li> <p>构造函数 public <strong>Semaphore</strong>(int permits)<br> permits 初始的可用许可数目。此值可能为负数,在这种情况下,必须在授予任何获取前进行释放。这里设置permits为1,因为这里只有一副刀叉:Semaphore s1 = new Semaphore(1)。</p> </li> <li> <p>public boolean <strong>tryAcquire</strong>(long timeout,TimeUnit unit)<br> 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。这里设置等待时间为1秒后:tryAcquire(1, TimeUnit.SECONDS)。</p> </li> <li> <p>public void <strong>release</strong>()<br> 释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。</p> </li> </ol> <h2><strong>用信号量控制之后的死锁避免模拟:</strong></h2> <pre> <code class="language-java">import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class UnlockTest { public static String th1 = "Dao"; public static String th2 = "Cha"; public static final Semaphore s1 = new Semaphore(1); public static final Semaphore s2 = new Semaphore(1); public static SimpleDateFormat sdf = new SimpleDateFormat("yyy/MM/dd hh:mm:ss"); public static void main(String[] args) { XiaoQiang xiaoQiang = new XiaoQiang(); new Thread(xiaoQiang).start(); XiaoMing xiaoMing = new XiaoMing(); new Thread(xiaoMing).start(); } } class XiaoQiang implements Runnable{ public void run() { try { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang starts asking for Dao and Cha......"); while(true){ if(UnlockTest.s1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang gets Dao!"); Thread.sleep(1000); // 休眠1秒(模拟等待下一个餐具或者等待1秒再取); if(UnlockTest.s2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang gets Cha!"); System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang happy eating!(30 seconds)"); Thread.sleep(30 * 1000); // 线程休眠30秒(模拟进餐); } else { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing fails to get Dao!"); } } else { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing fails to get Cha!"); } System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang releases Dao or Cha which has been obtained!"); UnlockTest.s1.release(); // 释放一个许可,将可用的许可数加1(模拟交出Dao的占用权);; UnlockTest.s2.release(); // 释放一个许可,将可用的许可数加1(模拟交出Cha的占用权);; System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoQiang occupies nothing......waiting 10sec!"); Thread.sleep(5 * 1000);//休眠5秒(模拟XiaoQiang交出餐具占用权后等待5秒再去获取餐具)。 } } catch (Exception e) { e.printStackTrace(); } } } class XiaoMing implements Runnable{ public void run() { try { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing starts asking for Dao and Cha......"); while(true){ if(UnlockTest.s2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing gets Cha!"); Thread.sleep(3000); // 休眠3秒(模拟等待下一个餐具或者等待3秒再取); if(UnlockTest.s1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing gets Dao!"); System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing happy eating!(30 seconds)"); Thread.sleep(30 * 1000); // 线程休眠30秒,模拟进餐; } else { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing fails to get Dao!"); } } else { System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing fails to get Cha!"); } System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing releases Dao or Cha which has been obtained!"); UnlockTest.s2.release(); // 释放一个许可,将可用的许可数加1(模拟交出Cha的占用权);; UnlockTest.s1.release(); // 释放一个许可,将可用的许可数加1(模拟交出Dao的占用权);; System.out.println(UnlockTest.sdf.format(new Date()).toString() + " XiaoMing occupies nothing......waiting 20sec!"); Thread.sleep(20 * 1000);//休眠20秒(模拟XiaoMing交出餐具占用权后等待20秒再去获取餐具)。 } } catch (Exception e) { e.printStackTrace(); } } }</code></pre> <p>编译运行之后的结果:</p> <blockquote> <p>2016/04/28 11:15:10 XiaoQiang starts asking for Dao and Cha......<br> 2016/04/28 11:15:10 XiaoMing starts asking for Dao and Cha......<br> 2016/04/28 11:15:10 XiaoQiang gets Dao!<br> 2016/04/28 11:15:10 XiaoMing gets Cha!<br> 2016/04/28 11:15:12 XiaoMing fails to get Dao!<br> 2016/04/28 11:15:12 XiaoQiang releases Dao or Cha which has been obtained!<br> 2016/04/28 11:15:12 XiaoQiang occupies nothing......waiting 10sec!<br> 2016/04/28 11:15:13 XiaoMing gets Dao!<br> 2016/04/28 11:15:13 XiaoMing happy eating!(30 seconds)<br> 2016/04/28 11:15:18 XiaoMing fails to get Cha!<br> 2016/04/28 11:15:18 XiaoQiang releases Dao or Cha which has been obtained!<br> 2016/04/28 11:15:18 XiaoQiang occupies nothing......waiting 10sec!<br> 2016/04/28 11:15:23 XiaoQiang gets Dao!<br> 2016/04/28 11:15:24 XiaoQiang gets Cha!<br> 2016/04/28 11:15:24 XiaoQiang happy eating!(30 seconds)<br> 2016/04/28 11:15:43 XiaoMing releases Dao or Cha which has been obtained!<br> 2016/04/28 11:15:43 XiaoMing occupies nothing......waiting 20sec!<br> 2016/04/28 11:15:54 XiaoQiang releases Dao or Cha which has been obtained!<br> 2016/04/28 11:15:54 XiaoQiang occupies nothing......waiting 10sec!<br> 2016/04/28 11:15:59 XiaoQiang gets Dao!<br> 2016/04/28 11:16:00 XiaoQiang gets Cha!<br> 2016/04/28 11:16:00 XiaoQiang happy eating!(30 seconds)<br> 2016/04/28 11:16:03 XiaoMing gets Cha!<br> 2016/04/28 11:16:06 XiaoMing gets Dao!<br> 2016/04/28 11:16:06 XiaoMing happy eating!(30 seconds)<br> ......</p> </blockquote> <p>输出信息多些更容易看出规律,由此可见:使用信号量控制之后已经成功地避免了死锁问题。</p> <p><br> </p> <p><a href="/misc/goto?guid=4959671886605297622">文/斯云</a>(简书)<br> </p>