JAVA并发,经典死锁案例-哲学家就餐

转自:http://blog.csdn.net/tayanxunhua/article/details/38691005

死锁经典案例:哲学家就餐。

这个案例会导致死锁。

通过修改《Java编程思想4》一书中的案例,来做实验,代码更易理解,结果也相对容易控制。

附代码:

筷子类:

 1 package com.tyxh.ch21.c6;
 2
 3 public class Chopstick {
 4     private boolean taken = false;//判断是此筷子是否被拿起
 5     public synchronized void take() throws InterruptedException {
 6         while(taken) {
 7             //如果已被拿起,则等待
 8             wait();
 9         }
10         //如果没有被拿起,则可以被拿起,并设置taken为true
11         taken = true;
12     }
13
14     public synchronized void drop() {
15         //放下筷子之后设置taken为false,并通知其他筷子
16         taken = false;
17         notifyAll();
18     }
19 }

哲学家类:

 1 package com.tyxh.ch21.c6;
 2
 3 import java.util.Random;
 4 import java.util.concurrent.TimeUnit;
 5
 6 public class Philosopher implements Runnable {
 7     private Chopstick left;//左筷子
 8     private Chopstick right;//右筷子
 9
10     private final int id;//哲学家编号
11     private final int ponderFactor;//根据这个属性设置思考时间
12
13     private Random rand = new Random(47);
14     private void pause() throws InterruptedException {
15         if(ponderFactor == 0) {
16             return;
17         }
18         TimeUnit.MILLISECONDS.sleep(rand.nextInt(ponderFactor *250));
19     }
20
21     public Philosopher(Chopstick left, Chopstick right, int ident, int ponder) {
22         this.left = left;
23         this.right = right;
24         this.id = ident;
25         this.ponderFactor = ponder;
26     }
27
28     public void run() {
29         try{
30             while(!Thread.interrupted()) {
31                 System.out.println(this + " " + "thinking");
32                 pause();
33                 right.take();
34                 System.out.println(this + " " + "拿右筷子");
35                 left.take();
36                 System.out.println(this + " " + "拿左筷子");
37                 pause();
38                 System.out.println(this + " " + "吃");
39                 right.drop();
40                 System.out.println(this + " " + "放下右筷子");
41                 left.drop();
42                 System.out.println(this + " " + "放下左筷子");
43             }
44         }catch(InterruptedException e) {
45             System.out.println(this + " 退出   ");
46         }
47     }
48
49     public String toString() {
50         return "Phiosopher : " + id;
51      }
52 }

测试类:

 1 package com.tyxh.ch21.c6;
 2
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 import java.util.concurrent.TimeUnit;
 6
 7 public class DeadlockingDiningPhilosophers {
 8     public static void main(String[] args) throws InterruptedException {
 9         int ponder = 5;
10         if(args.length > 0) {
11             ponder = Integer.parseInt(args[0]);
12         }
13         int size = 5;
14         if(args.length > 1) {
15             size = Integer.parseInt(args[1]);
16         }
17         ExecutorService exec = Executors.newCachedThreadPool();
18         Chopstick[] stick = new Chopstick[size];
19
20         for(int i = 0; i < size; i++) {
21             stick[i] = new Chopstick();
22         }
23
24         for(int i = 0; i < size; i++) {
25             Philosopher p = new Philosopher(stick[i], stick[(i+1)%size], i, ponder);
26             exec.execute(p);
27         }
28
29         TimeUnit.SECONDS.sleep(3);
30         exec.shutdownNow();
31
32     }
33 }
34  

可以通过命令行参数调整ponder因子设置哲学家思考时间,也可以设置筷子及哲学家的数量size。

死锁产生的四个必要条件。

1>互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2>不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3>请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的战友。

4>循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

这里仅给出书中处理此死锁的解决方案(破坏第四种条件):

方案是:

前面哲学家拿筷子的顺序都是先拿右,再拿左,但最后一个哲学家拿筷子的顺序是先拿左,再拿右,就可以通过阻止循环等待这个死锁的条件来阻止死锁发生。

即将代码:

1         for(int i = 0; i < size; i++) {
2             Philosopher p = new Philosopher(stick[i], stick[(i+1)%size], i, ponder);
3             exec.execute(p);
4         }

修改为:

1         for(int i = 0; i < size; i++) {
2             if(i < size - 1) {
3                 Philosopher p = new Philosopher(stick[i], stick[(i+1)%size], i, ponder);
4                 exec.execute(p);
5             }else {
6                 Philosopher p = new Philosopher(stick[0], stick[i], i, ponder);
7                 exec.execute(p);
8             }
9         }
时间: 2024-12-24 14:57:28

JAVA并发,经典死锁案例-哲学家就餐的相关文章

Java并发编程(十)死锁

哲学家进餐问题 并发执行带来的最棘手的问题莫过于死锁了,死锁问题中最经典的案例就是哲学家进餐问题:5个哲学家坐在一个桌子上,桌子上有5根筷子,每个哲学家的左手边和右手边各有一根筷子.示意图如下: 哲学家进餐问题 并发执行带来的最棘手的问题莫过于死锁了,死锁问题中最经典的案例就是哲学家进餐问题:5个哲学家坐在一个桌子上,桌子上有5根筷子,每个哲学家的左手边和右手边各有一根筷子.示意图如下: 哲学家必须拿起左右两边的筷子才能进餐,如果他们同时拿起左手边的筷子,就会导致死锁.因为右手边的筷子被他右边的

Thking in Java---从哲学家就餐问题看死锁现象

我们知道一个对象可以有synchronized方法或其他形式的加锁机制来防止别的线程在互斥还没释放的时候就访问这个对象.而且我们知道线程是会变成阻塞状态的(挂起),所以有时候就会发生死锁的情况:某个任务在等待另一个任务,而后者又在等待其它任务,这样一直下去,知道这个链条下的任务又在等待第一个任务释放锁,这样就形成了一个任务之间相互等待的连续循环,没有任务可以继续的情况.死锁的最大问题在于它发生的几率非常小,并不是我们一运行程序它就死锁了,而是会不知道那个时候程序就死锁了并且我们很难重现当时出现死

Java 并发编程之图形界面应用程序及死锁问题

不知道为什么这本书还要讲一个界面应用程序,Java的界面做的很糟糕,效率低下,而且界面是java的弱项,可能是因为这里边是有一些并发编程的知识吧. 为什么GUI是单线程的 无论是Swing还是AWT都是单线程的.但它不仅限于在java中,在Qt,NexiStep,macOs CoCoa X windows以及其它环境中的GUI框架都是单线程的,许多人都曾经尝试编写多线程的GUI框架,但最终都由于竞态条件和死锁导致的稳定性问题而又重新回到单线程的事件队列模型:采用一个专门的线程从队列中抽取事件,并

从哲学家就餐问题彻底认识死锁

第一节  哲学家就餐问题 第二节  什么是死锁 第三节  死锁的定义 第四节  死锁发生的条件 第五节  如何避免死锁 5.1 动态避免,银行家算法(杠杆分配),在资源分配上下文章 5.2 静态避免,从任务代码上避免死锁 第六节  死锁的综合治理 第一节  哲学家就餐问题 假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考.吃东西的时候,他们就停止思考,思考的时候也停止吃东西.餐桌中间有一大碗意大利面,每两个哲学家之间有一双筷子.因为用一只筷子很难吃到意大利面,所以假设哲学

【Java并发基础】死锁

前言 我们使用加锁机制来保证线程安全,但是如果过度地使用加锁,则可能会导致死锁.下面将介绍关于死锁的相关知识以及我们在编写程序时如何预防死锁. 什么是死锁 学习操作系统时,给出死锁的定义为两个或两个以上的线程在执行过程中,由于竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去.简化一点说就是:一组相互竞争资源的线程因为互相等待,导致"永久"阻塞的现象. 下面我们通过一个转账例子来深入理解死锁. class Account { private int balance; /

java多线程经典案例

/** * 典型案例:子线程执行10次,主线程执行100次,两者交替50次. */ package cn.itcast.lesson4; public class TestWaitNotify { public static void main(String[] args){ final Business business= new Business(); new Thread( new Runnable() { public void run() { for(int i=1;i<=50;i++

17、Java并发性和多线程-避免死锁

以下内容转自http://ifeve.com/deadlock-prevention/: 在有些情况下死锁是可以避免的.本文将展示三种用于避免死锁的技术: 加锁顺序 当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生. 如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生.看下面这个例子: Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3: wait for A

基于CAS线程安全的计算方法 java并发编程的艺术上的一个案例

package thread; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /**  * @author  changxiangxiang  * @date 2014年8月6日 下午3:25:12  * @description  * @since  sprint2  */ public class Counter {     privat

java并发-线程饥饿死锁测试

线程饥饿死锁 <Java并发编程实践>中对线程饥饿死锁的解释是这样的:在使用线程池执行任务时,如果任务依赖于其他任务,那么就可能产生死锁问题.在单线程的Executor中,若果一个任务将另一个任务提交到同一个Executor,并且等待这个被提交的任务的结果,那么这必定会导致死锁.第一个任务在工作队列中,并等待第二个任务的结果:而第二个任务则处于等待队列中,等待第一个任务执行完成后被执行.这就是典型的线程饥饿死锁.即使是在多线程的Executor中,如果提交到Executor中的任务之间相互依赖