一个对象可以有 synchronized 方法或其他形式的加锁机制来防止别的任务在互斥还没有释放的时候就访问这个对象。

  死锁

  任务有可能变成阻塞状态,所以就可能发生这样的情况:某个任务在等待另一个任务,而后者又在等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁。这就形成了一个相互等待的循环,没有那个线程能够继续。这被称之为死锁。

  我们真正需要解决的问题是程序看起来可能工作良好,但是具有潜在的死锁风险。这时,死锁可能发生,而事先却没有任何征兆,所以缺陷会潜伏在你的程序里,直到被人以外的发现了。因此,在编写并发编程的时候,进行仔细的程序设计以防止死锁是非常关键的。

  下面引入一个问题,一共有 5 个哲学家。这些哲学家将花部分时间思考,花部分时间就餐。作为哲学家他们很穷,所以他们只能买 5 根筷子。他们围坐在桌子的周围,每人之间放一根筷子。当一个哲学家要就餐的时候,这个哲学家必须同时得到左边和右边的筷子。如果一个哲学家左边或者右边已经得到筷子,那么这个哲学家就必须等待,直至可得到必须的筷子。

  public class Chopstick {

  private boolean taken = false;

  public synchronized void take() throws InterruptedException{

  while (taken) {

  wait();

  }

  taken = true;

  }

  public synchronized void drop() {

  taken = false;

  notifyAll();

  }

  }

  任何两个哲学家都不能使用同一根筷子。也就是不能同时 taken() 同一个筷子。另外如果一个 Chopstick 被一个哲学家获得,那么另一个哲学家可以 wait(),直到当前的这根筷子的持有者调用 drop() 结束使用。

  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);

  public void pause() throws InterruptedException{

  if (ponderFactor ==0) {

  return;

  }

  TimeUnit.MICROSECONDS.sleep(random.nextInt(ponderFactor * 250));

  }

  protected Philosopher(Chopstick left, Chopstick right, int id, int ponderFactor) {

  super();

  this.left = left;

  this.right = right;

  this.id = id;

  this.ponderFactor = ponderFactor;

  }

  @Override

  public void run() {

  // TODO Auto-generated method stub

  try {

  while (!Thread.interrupted()) {

  System.out.println(this+开始思考);

  pause();

  System.out.println(this+开始拿左边的筷子);

  left.take();

  System.out.println(this+开始拿右边的筷子);

  right.take();

  System.out.println(this+开始就餐);

  pause();

  left.drop();

  right.drop();

  }

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  System.out.println(当前线程被中断了);

  }

  }

  @Override

  public String toString() {

  // TODO Auto-generated method stub

  return 哲学家的编号:+id;

  }

  }

  在哲学家的任务中,每个哲学家都是不断的思考和吃饭。如果 ponderFactor 不为 0,则 pause() 就会休眠一会。通过这样的方法你会看到哲学家会思考一段时间。然后尝试着去获取左边和右边的筷子,随后再在吃饭上花掉一段随机的时间,之后重复此过程。

  现在我们来建立这个程序的死锁版本:

  public class DeadLockingDiningPhilosophers {

  public static void main(String[] args) {

  // TODO Auto-generated method stub

  int ponder = 5;

  int size = 5;

  ExecutorService service = Executors.newCachedThreadPool();

  Chopstick[] sChopsticks = new Chopstick[size];

  for (int i = 0; i size; i++) {

  sChopsticks[i] = new Chopstick();

  }

  for (int i = 0; i size; i++) {

  //每一个哲学家都会持有他左边和右边的筷子对象

  service.execute(new Philosopher(sChopsticks[i],sChopsticks[(i+1)%size],i,ponder));

  }

  System.out.println(执行结束);

  service.shutdownNow();

  }

  }

  执行的结果:

  执行结束

  哲学家的编号:2开始思考

  哲学家的编号:4开始思考

  哲学家的编号:1开始思考

  哲学家的编号:0开始思考

  哲学家的编号:3开始思考

  当前线程被中断了

  当前线程被中断了

  当前线程被中断了

  当前线程被中断了

  当前线程被中断了

  这个程序表示每一个哲学家都有可能要表示进餐,从而等待其临近的 Philosopher 放下他们的 Chopstick。这将会使得程序死锁。

  要修正死锁必须明白,当以下四个条件同时满足时,就会发生死锁:

  互斥条件。任务使用的资源中至少有一个是不能共享的。

  至少有一个任务必须持有一个资源,并且正在等待获取一个当前被别的任务持有的资源。也就是说必须是拿着一根筷子等待另一个筷子。

  资源不能被任务抢占,任务必须把资源释放当做普通事件。你不能抢别人手里的筷子。

  必须有循环等待,这时一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样循环下去直到有一个任务等待第一个任务所持有的资源,使得大家都被锁住。

  因为要发生死锁所有这些条件必须满足;所以要防止死锁的话只需要破坏其中一个就可以。在程序中防止死锁的最容易的办法就是破坏第四个循环条件。

  public class FixedDiningPhilosophers {

  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++)

  if(i (size-1))

  exec.execute(new Philosopher(sticks[i], sticks[i+1], i, ponder));

  else

  exec.execute(new Philosopher(sticks[0], sticks[i], i, ponder));

  if(args.length == 3 args[2].equals(timeout))

  TimeUnit.SECONDS.sleep(5);

  else {

  System.out.println(Press 'Enter' to quit);

  System.in.read();

  }

  exec.shutdownNow();

  }

  }

  执行结果:

  哲学家的编号:2开始拿左边的筷子

  哲学家的编号:4开始思考

  哲学家的编号:1开始思考

  哲学家的编号:0开始拿左边的筷子

  哲学家的编号:0开始拿右边的筷子

  哲学家的编号:0开始就餐

  哲学家的编号:3开始就餐

  哲学家的编号:2开始拿右边的筷子

  /.....

  通过确保最后一个哲学家先拿起和放下左边的筷子,我们可以移除死锁,从而使得程序运行。Java 并没有对死锁提供类库上的支持;能否通过仔细的程序设计避免死锁需要我们自己努力。

java编程思想之并发(死锁)的更多相关文章

  1. Java编程思想 第21章 并发

    这是在2013年的笔记整理.现在重新拿出来,放在网上,重新总结下. 两种基本的线程实现方式 以及中断 package thread; /** * * @author zjf * @create_tim ...

  2. 《Java编程思想》读书笔记(五)

    前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ...

  3. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象2.程序就是对象的集合3.每个对象都是由其它对象所构成 ...

  4. Java编程思想总结笔记The first chapter

    总觉得书中太啰嗦,看完总结后方便日后回忆,本想偷懒网上找别人的总结,无奈找不到好的,只好自食其力,尽量总结得最好. 第一章  对象导论 看到对象导论觉得这本书 目录: 1.1 抽象过程1.2 每个对象 ...

  5. Java编程思想读书笔记(一)【对象导论】

    2018年1月7日15:45:58 前言 作为学习Java语言的经典之作<Java编程思想>,常常被人提起.虽然这本书出版十年有余,但是内容还是很给力的.很多人说这本书不是很适合初学者,我 ...

  6. 《Java编程思想》读书笔记

    前言 这个月一直没更新,就是一直在读这本<Java编程思想>,这本书可以在Java业界被传神的一本书,无论谁谈起这本书都说好,不管这个人是否真的读过这本书,都说啊,这本书很好.然后再看这边 ...

  7. Java编程思想(前十章)

    Java编程思想 有C++编程基础的条件下, 前10章可以快速过一下,都是基本语法,不需要花太多时间. 着重中后段的一些章节,类型信息.泛型.容器.IO.并发等. 中文翻译版 阅读地址 对于一个架构师 ...

  8. 《Java编程思想第四版完整中文高清版.pdf》-笔记

    D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long ...

  9. JAVA编程思想读书笔记(五)--多线程

    接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/detail ...

随机推荐

  1. cocos代码研究(5)Action学习笔记

    理论部分 Action类也是cocos核心基础类之一,在游戏中起着非常重要的作用,继承自Ref,被 FiniteTimeAction(有限时间动作), Follow , 以及 Speed 继承. 有限 ...

  2. SV中的task和function

    SV中class的properties和methods默认都是public的,但是可以声明为local和protected. 一个properties声明为local类型的,则只在该class中的me ...

  3. 网页中自适应的显示PDF

    PDF格式呢,是一个高大的新式,如何在不同的浏览器中自适应显示,是一个值得研究的问题. 这里说明重点部分:获取浏览器宽高. IE中: document.body.clientWidth ==> ...

  4. 学号20155311 2016-2017-2 《Java程序设计》第9周学习总结

    学号 2016-2017-2 <Java程序设计>第9周学习总结 教材学习内容总结 整合数据库 JDBC(Java DataBase Connectivity)即java数据库连接,是一种 ...

  5. 搭建Linux-java web运行环境之一:安装jdk+tomcat

    环境 OS:Red Hat Enterprise Linux Server release 7.3 (Maipo) JDK:jdk-7u80-linux-x64.tar.gz Tomcat:apach ...

  6. Java设计模式应用——过滤器模式

    storm引擎计算出一批中间告警结果,会发送一条kafka消息给告警入库服务,告警入库服务接收到kafka消息后读取中间告警文件,经过一系列处理后把最终告警存入mysql中. 实际上,中间告警结果可能 ...

  7. hue, saturation, and brightness:色调、饱和度和亮度

    色调.饱和度和亮度(hue, saturation, and brightness)以人对红.绿.蓝(RGB)三色组合的感觉为基础.在描述阴极射线管显示器参数时,经常提到这三个专有名词.所有的颜色可以 ...

  8. corejDay1

    1.内部类: 有什么用? 1.可以访问该类定义所在作用域中的数据,包括私有数据. 2.当想定义一个回调函数而不想编写大量代码时,使用匿名内部类比较便捷. 3.内部类可以对同一个包中的其他类隐藏起来. ...

  9. centos+Jenkins+maven搭建持续集成

    Jenkins是一个开源软件项目,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能 什么是持续集成 随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成 ...

  10. 03: vuejs 事件、模板、过滤器

    目录:Vue其他篇 01: vue.js安装 02: vue.js常用指令 03: vuejs 事件.模板.过滤器 目录: 1.1 事件 1.2 模板 1.3 自定义过滤器 1.4 过度 1.5 支付 ...