java编程思想之并发(死锁)
一个对象可以有 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编程思想之并发(死锁)的更多相关文章
- Java编程思想 第21章 并发
这是在2013年的笔记整理.现在重新拿出来,放在网上,重新总结下. 两种基本的线程实现方式 以及中断 package thread; /** * * @author zjf * @create_tim ...
- 《Java编程思想》读书笔记(五)
前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ...
- Java编程思想 (1~10)
[注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象2.程序就是对象的集合3.每个对象都是由其它对象所构成 ...
- Java编程思想总结笔记The first chapter
总觉得书中太啰嗦,看完总结后方便日后回忆,本想偷懒网上找别人的总结,无奈找不到好的,只好自食其力,尽量总结得最好. 第一章 对象导论 看到对象导论觉得这本书 目录: 1.1 抽象过程1.2 每个对象 ...
- Java编程思想读书笔记(一)【对象导论】
2018年1月7日15:45:58 前言 作为学习Java语言的经典之作<Java编程思想>,常常被人提起.虽然这本书出版十年有余,但是内容还是很给力的.很多人说这本书不是很适合初学者,我 ...
- 《Java编程思想》读书笔记
前言 这个月一直没更新,就是一直在读这本<Java编程思想>,这本书可以在Java业界被传神的一本书,无论谁谈起这本书都说好,不管这个人是否真的读过这本书,都说啊,这本书很好.然后再看这边 ...
- Java编程思想(前十章)
Java编程思想 有C++编程基础的条件下, 前10章可以快速过一下,都是基本语法,不需要花太多时间. 着重中后段的一些章节,类型信息.泛型.容器.IO.并发等. 中文翻译版 阅读地址 对于一个架构师 ...
- 《Java编程思想第四版完整中文高清版.pdf》-笔记
D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long ...
- JAVA编程思想读书笔记(五)--多线程
接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/detail ...
随机推荐
- Vim/Vi的使用
Vim 是vi的加强 Gvim图形化的vim Vim/Vi简介 Vim/Vi是一个功能强大的全屏幕文本编辑器,是Linux/Unix上最常用的文本编辑器,他们 的作用是建立,编辑,显示文本文件 Vi ...
- http协议基础(九)响应首部字段
响应首部字段: 服务器向客户端返回响应报文中所使用的字段,用于补充的附加信息.服务器信息.以及对客户端的附加要求等 1.Accept-Ranges 告知客户端服务器能否处理范围请求,以指定获取服务器的 ...
- linux字符处理命令 sort(部分转载)
[root@LocalWeb01 ~]# sort /etc/passwd |less (升序 ) [root@LocalWeb01 ~]# sort -r /etc/passwd |less ( ...
- 学习Css补充知识点
1.text-transform: capitalize;UpperCase等 2.border-size:box,边框在定义的宽高范围内画, content默认,在宽高外画.元互的宽高只指内容 3 ...
- ide vscode安装
在linux系统中安装VSCode(Visual Studio Code) 在linux系统中安装VSCode(Visual Studio Code) 1.从官网下载压缩包(话说下载下来解压就直接 ...
- python 命令执行文件传递参数
import os,sys for root,dirs,files in os.walk(sys.argv[1]): for name in files: print(os.path.join(roo ...
- python写一个密码生成器的类,要求有个类变量,统计一下一共生成过多少个密码。 要求有4个方法,1:构造方法 2 实例方法 3 类方法 4 静态方法
生成指定长度的随机数字密码 生成指定长度的随机字母密码 生成指定长度的随机数字和字母的混合 #encoding=utf-8 import random import string class pa ...
- Vert.x
Vert.x是一个基于JVM.轻量级.高性能的应用平台,非常适用于最新的移动端后台.互联网.企业应用架构.Vert.x基于全异步Java服务器Netty,并扩展出了很多有用的特性. 同时支持多种编程语 ...
- python3.4学习笔记(二十一) python实现指定字符串补全空格、前面填充0的方法
python3.4学习笔记(二十一) python实现指定字符串补全空格.前面填充0的方法 Python zfill()方法返回指定长度的字符串,原字符串右对齐,前面填充0.zfill()方法语法:s ...
- 04: 使用BeautifulSoup封装的xss过滤模块
目录: 1.1 xss攻击简介 1.2 xss攻击解决方法 1.1 xss攻击简介返回顶部 1.简介 1. 跨站脚本(cross site script)为了避免与样式css混淆,所以简称为XSS. ...