一个对象可以有 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. docker搭建本地仓库并制作自己的镜像

    原文地址https://blog.csdn.net/junmoxi/article/details/80004796 1. 搭建本地仓库1.1 下载仓库镜像1.2 启动仓库容器2. 在CentOS容器 ...

  2. html select 和dropdownlist小结收集

    //html select var x = $("#selectSort").val();  //获取选中的value值 获取select选中的索引: $("#selec ...

  3. 《Bilateral Multi-Perspective Matching for Natural Language Sentences》(句子匹配)

    问题: Natural language sentence matching (NLSM),自然语言句子匹配,是指比较两个句子并判断句子间关系,是许多任务的一项基本技术.针对NLSM任务,目前有两种流 ...

  4. 搭建Linux-java web运行环境之二:安装mysql

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

  5. zookeeper 详解

    是 分布式 协调 服务. ZK的工作:注册:所有节点向ZK争抢注册,注册成功会建立一套节点目录树,先注册的节点为Active节点,后注册节点成为standby;监听事件:节点在ZK集群里注册监听动作: ...

  6. java接口对接——别人调用我们接口获取数据

    java接口对接——别人调用我们接口获取数据,我们需要在我们系统中开发几个接口,给对方接口规范文档,包括访问我们的接口地址,以及入参名称和格式,还有我们的返回的状态的情况, 接口代码: package ...

  7. keepalived+MySQL高可用集群

    基于keepalived搭建MySQL的高可用集群   MySQL的高可用方案一般有如下几种: keepalived+双主,MHA,MMM,Heartbeat+DRBD,PXC,Galera Clus ...

  8. 01: html常用标签

    目录: 1.1 web开发的三把利器介绍 1.2 网页头部head标签中几个常用标签 1.3 html常用标签归类 1.4 input系列标签 1.5 HTML其他标签 1.1 web开发的三把利器介 ...

  9. 记录openwrt下补丁apply的过程中出错,但是可以单独打上该补丁

    背景: 在openwrt的编译框架下无法正确打上补丁,而单独使用git却可以成功 这个补丁到底与其它补丁有何不同? 该补丁的生成的过程解析: 旧文件:vi 打开旧文件会提示no newline at ...

  10. HDU1251 统计难题 (字典树模板)题解

    思路:模板题,贴个模板 代码: #include<cstdio> #include<cstring> #include<cstdlib> #include<q ...