该例子说明了4点出现死锁需要同时满足的条件:
- 互斥条件:任务使用的资源至少有一个是不能共享的。这里,一根chopstick(筷子)一次就只能让一个philosopher(哲学家)使用。
- 至少有一个任务它必须持有一个资源且正在等待获取另一个当前被别的任务持有的资源,也就是说,要发生死锁,philosopher必须拿着一根筷子并且等待另一根筷子。
- 资源不能被任务抢占,任务必须把资源释放当作普通事件。philosopher很有礼貌,他们不会从其他的philosoper那里抢筷子。
必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadLockingDiningPhilosopher.java中,因为每个philosopher都试图先得到右边的筷子再得到左边的筷子,所以发生循环等待。
因为要发生死锁,所有这些条件都必须满足,要防止死锁的话,破坏其中一个条件即可,在程序中,防止死锁的最容易的方法是破坏第4个条件,即循坏等待条件,本例中,就是最后一个哲学家先拿起左边的筷子,再拿起右边的筷子。这样就可以防止循环等待。
Chockstick.java:筷子定义类
package philo;
public class Chopstick {
private boolean taken = false;
public synchronized void take() throws InterruptedException { //当一个任务拿起该筷子时,将taken赋值true,别的任务就在take()上等待
while(taken)
wait();
taken = true;
}
public synchronized void drop(){ //当持有筷子的任务drop时,taken赋值false,在该筷子对象的taken()上等待的任务就可以获得该筷子
taken = false;
notifyAll();
}
}
Philosopher.java:哲学家定义类
package philo;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Philosopher implements Runnable {
private Chopstick left;
private Chopstick right;
private final int id;
private final int ponderFactor;
private Random random = new Random(47);
private void pause() throws InterruptedException {
if(ponderFactor == 0) return;
TimeUnit.MILLISECONDS.sleep(random.nextInt(ponderFactor * 250));
}
public Philosopher(Chopstick left, Chopstick right, int ident, int ponder){
this.left = left;
this.right = right;
this.id = ident;
this.ponderFactor = ponder;
}
public void run() { //起一个任务(线程)
try {
while(! Thread.interrupted()) {
System.out.println(this +" " + "thinking");
pause();
System.out.println(this +" " + "grabbing right");
right.take(); //该哲学家首先拿起右边的筷子
System.out.println(this + " " + "grabbing left");
left.take(); // 该哲学家然后拿起左边的筷子,此处可能发生的情况:1、成功拿起;2、等待左边的哲学家drop掉右手的筷子,
//若每一个哲学家都拿着右手的等着左手的筷子,并且最后一个哲学家左手等待第一个哲学家右手的筷子,就会循坏等待发生死锁。
System.out.println(this + " " + "is eating");//若该哲学家成功拿起左右手筷子,则吃饭,下一步并释放手中筷子
pause();
right.drop();
left.drop();
}
} catch (InterruptedException e) {
System.out.println(this + " " + "exiting via interruption");
}
}
public String toString(){
return "Philosopher" + id;
}
}
DeadLockingDiningPhilosopher.java :产生死锁的哲学家吃饭main类:including main method
package philo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DeadLockingDiningPhilosopher {
public static void main(String[] args) throws Exception{
int ponder = 5;
if(args.length> 0)
ponder = Integer.parseInt(args[0]);
int size = 5;
if(args.length >1)
size = Integer.parseInt(args[1]);
ExecutorService exec = Executors.newCachedThreadPool();
Chopstick[] sticks = new Chopstick[size];
for(int i=0; i<size; i++)
sticks[i] = new Chopstick();
for(int i =0; i< size; i++)
exec.execute(new Philosopher(sticks[i], sticks[(i+1) % size], i, ponder)); //n个哲学家n支筷子循坏使用
if(args.length == 3 && args[2].equals("timeout"))
TimeUnit.SECONDS.sleep(5);
else {
System.out.println("please Press 'Enter' to quit");
System.in.read();
}
exec.shutdown();
}
}
注:在这里,需要输入命令行参数,一共能三个参数分别是:arg[0]:哲学家思考、吃饭、放下筷子的间隔时间,这里设置为0可以让死锁发生的机会变大;args[1]:哲学家数量;args[2]:timeout暂时没想出用处 .