我们在学习操作系统的时候,一定接触过哲学家就餐的问题,这是由Edsger Dijkstra提出的经典的死锁例子,该例子描述的是,指定
五个哲学家一起就餐,这些哲学家花一部分时间就餐,一部分时间思考,就餐时,总共五把叉子,每人一把,如果需要就餐就必须拿
到左边或右边的叉子,直到手中有两把叉子时才能就餐,否则哲学家就必须等待,直到得到必须的叉子。
下面我们就编写程序来实现这样一个场景
public class Test { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); Fork [] array = new Fork[5]; for (int i = 0; i < 5; i++) { array[i] = new Fork(); } for (int i = 0; i < array.length; i++) { exec.execute(new Philosopher(array[i], array[(i+1)%5],5,i)); } try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } exec.shutdownNow(); } } class Philosopher implements Runnable{ Fork forkLeft,forkRight; int time;//休息时间 int num;//哲学家编号 Random rand = new Random(); public Philosopher(Fork forkLeft,Fork forkRight,int time,int num){ this.forkLeft = forkLeft; this.forkRight = forkRight; this.time = time; this.num = num; } @Override public void run() { try { while(!Thread.interrupted()){ forkLeft.take(); forkRight.take(); rest(); forkLeft.drop(); forkRight.drop(); System.out.println("哲学家"+num+"号已经吃了一次"); } } catch (InterruptedException e) { System.out.println("结束用餐"); } } public void rest(){ try { TimeUnit.MILLISECONDS.sleep(rand.nextInt(time*250)); } catch (InterruptedException e) { e.printStackTrace(); } } } class Fork{ private boolean isTake = false; public synchronized void take()throws InterruptedException{ while(isTake){ wait(); } isTake = true; } public synchronized void drop()throws InterruptedException{ isTake = false; notifyAll(); } }
上面的例子,我只让他运行了十秒,在这个时间段你可能看不到死锁,如果你永久的运行下面可能就会产生死锁。为什么会产生死锁呢?从
上面的例子中我们可以看到,哲学家在用餐的时候都是先获取左边的叉子,然后获取右边的叉子,如果获取到了左边的叉子,在获取右边的叉子
时,右边的叉子已经被人获取了,那么他就处于等待状态,同时没有释放之前获取的左边的叉子。假如他们都处于这样一个场景:0号哲学家获取
了左边4号哲学家的叉子,1号获取0号的,2号获取1号的,3号获取2号的,4号获取3号的,只要遇到这样的场景,那么就永远不会有人同时拿到
2把叉子,所有的人都会永久的等待下去,这样就造成了死锁。
造成死锁的几个必要条件:
1、互斥条件,至少有一个条件是不能共享的,这里的叉子就不能同时被两个人使用。
2、循环等待,一个任务等待另一个任务所持有的资源,而另一个任务又在等待前者所持有的资源。这里如果哲学家都相互持有另外一个哲学家的
叉子后,就会相互等待另一方释放叉子,那么就会造成循环等待的场景。
3、非抢占,任何任务所持有的资源,除非自己释放掉,否则其他任务不能抢占。
4、持有等待,至少有一个任务必须持有一个资源,并且正在等待获取一个当前被其他任务持有的资源。这里哲学家获取到了左边的人的叉子,如果
右边的人叉子已经被其他的人获取,那么这个哲学家就会持有左边的人的叉子,并且等待其他人释放右边的人的叉子。
那么怎样来预防上面这个可能会产生死锁的例子呢?只要破坏上面4个条件中的一个就会打破死锁的局面,在这里我们打破第二个条件就会消除死锁
只要其中有一人先获取右边再获取左边的叉子就会打破这种局面,这样就会使得桌子上至少能够存在一对叉子可以使用,这一死锁便解除了。