package com.tl.skyLine.thread;import java.util.Date;/** * Created by tl on 17/3/3. */public class DeadLock { public static String bowl = "碗"; public static String chopsticks = "筷子"; public static void main(String[] args) { LockA la = new LockA(); new Thread(la).start(); LockB lb = new LockB(); new Thread(lb).start(); }}class LockA implements Runnable { public void run() { try { System.out.PRintln(new Date().toString() + "邹保健开始拿餐具吃饭"); while (true) { synchronized (DeadLock.bowl) { System.out.println(new Date().toString() + "邹保健抢到了碗"); Thread.sleep(3000); // 此处等待是给B能锁住机会 synchronized (DeadLock.chopsticks) { System.out.println(new Date().toString() + "邹保健抢到了筷子"); Thread.sleep(60 * 1000); // 为测试,占用了就不放 } } } } catch (Exception e) { e.printStackTrace(); } }}class LockB implements Runnable { public void run() { try { System.out.println(new Date().toString() + "陈顶天开始拿餐具吃饭"); while (true) { synchronized (DeadLock.chopsticks) { System.out.println(new Date().toString() + "陈顶天抢到了筷子"); Thread.sleep(3000); // 此处等待是给A能锁住机会 synchronized (DeadLock.bowl) { System.out.println(new Date().toString() + "陈顶天抢到了碗"); Thread.sleep(60 * 1000); // 为测试,占用了就不放 } } } } catch (Exception e) { e.printStackTrace(); } }}结果:Fri Mar 03 16:34:36 CST 2017陈顶天开始拿餐具吃饭Fri Mar 03 16:34:37 CST 2017陈顶天抢到了筷子Fri Mar 03 16:34:36 CST 2017邹保健开始拿餐具吃饭Fri Mar 03 16:34:37 CST 2017邹保健抢到了碗结果陈顶天同学抢到了参筷子,拿着不放,邹保健同学抢到了碗,也死活不放手,但是只有一双筷子和一双碗,结果就是双双饿死。。。
3.避免死锁
假如我们是陈顶天和邹保健同学的同事,肯定不忍心看到他们饿死,那么怎么办呢?
为了解决这个问题,我们不使用显示的去锁,我们用信号量Semaphore去控制。信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。
package com.tl.skyLine.thread;import java.util.Date;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;/** * Created by tl on 17/3/3. */public class UnDeadLock { public static String bowl = "碗"; //信号量可以碗只能能被一个线程同时访问 public static final Semaphore a1 = new Semaphore(1); public static String chopsticks = "筷子"; //信号量可以筷子只能能被一个线程同时访问 public static final Semaphore a2 = new Semaphore(1); public static void main(String[] args) { LockAa la = new LockAa(); new Thread(la).start(); LockBa lb = new LockBa(); new Thread(lb).start(); }}class LockAa implements Runnable { public void run() { try { System.out.println(new Date().toString() + "邹保健开始拿餐具吃饭"); while (true) { if (UnDeadLock.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "邹保健抢到了碗"); if (UnDeadLock.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "邹保健抢到了筷子,凑齐了餐具,准备吃饭"); Thread.sleep(60 * 1000 * 10); // 抢到餐具就开始吃饭,吃饭时间十分钟 } else { System.out.println(new Date().toString() + "筷子已经被抢走了,邹保健抢筷子失败"); } } else { System.out.println(new Date().toString() + "碗已经被抢走了,邹保健抢碗失败"); } UnDeadLock.a1.release(); // 释放 UnDeadLock.a2.release(); System.out.println(new Date().toString() + "邹保健把抢到的部分餐具又放回原处"); Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的 } } catch (Exception e) { e.printStackTrace(); } }}class LockBa implements Runnable { public void run() { try { System.out.println(new Date().toString() + "陈顶天开始拿餐具吃饭"); while (true) { if (UnDeadLock.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "陈顶天抢到了筷子"); if (UnDeadLock.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "陈顶天抢到了碗,凑齐了餐具,准备吃饭"); Thread.sleep(60 * 1000 * 10); // 抢到餐具就开始吃饭,吃饭时间十分钟 } else { System.out.println(new Date().toString() + "碗已经被抢走了,陈顶天抢碗失败"); } } else { System.out.println(new Date().toString() + "筷子已经被抢走了,陈顶天抢筷子失败"); } UnDeadLock.a1.release(); // 释放 UnDeadLock.a2.release(); System.out.println(new Date().toString() + "陈顶天把抢到的部分餐具又放回原处"); Thread.sleep(10 * 1000);//这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁 } } catch (Exception e) { e.printStackTrace(); } }}结果:Fri Mar 03 18:12:07 CST 2017邹保健开始拿餐具吃饭Fri Mar 03 18:12:07 CST 2017陈顶天开始拿餐具吃饭Fri Mar 03 18:12:07 CST 2017邹保健抢到了碗Fri Mar 03 18:12:07 CST 2017陈顶天抢到了筷子Fri Mar 03 18:12:08 CST 2017筷子已经被抢走了,邹保健抢筷子失败Fri Mar 03 18:12:08 CST 2017邹保健把抢到的部分餐具又放回原处Fri Mar 03 18:12:08 CST 2017陈顶天抢到了碗,凑齐了餐具,准备吃饭Fri Mar 03 18:12:10 CST 2017碗已经被抢走了,邹保健抢碗失败Fri Mar 03 18:12:10 CST 2017邹保健把抢到的部分餐具又放回原处Fri Mar 03 18:12:11 CST 2017邹保健抢到了碗Fri Mar 03 18:12:11 CST 2017邹保健抢到了筷子,凑齐了餐具,准备吃饭很明显看到,我们打破了满足死锁的第三条,即当资源请求者在请求其他的资源的同时保持对原有资源的占有,当没有完全抢到碗和筷子的时候,两个人(线程)全部释放占有的资源,重新开始争抢资源,这样一个人抢到碗和筷子之后,吃饱再给另一个吃,这样你就成功了拯救了你的两位同事!Semaphore api:
acquirepublic void acquire() throws InterruptedException 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。 如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态: 某些其他线程调用此信号量的 release() 方法,并且当前线程是下一个要被分配许可的线程;或者 其他某些线程中断当前线程。 如果当前线程: 被此方法将其已中断状态设置为 on ;或者 在等待许可时被中断。 则抛出 InterruptedException,并且清除当前线程的已中断状态。 抛出: InterruptedException - 如果当前线程被中断releasepublic void release() 释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。 不要求释放许可的线程必须通过调用 acquire() 来获取许可。通过应用程序中的编程约定来建立信号量的正确用法。wait()与sleep()的区别:sleep()方法只让出了CPU,而并不会释放同步资源锁!!!wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行;
新闻热点
疑难解答