博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程的死锁
阅读量:5914 次
发布时间:2019-06-19

本文共 4174 字,大约阅读时间需要 13 分钟。

俗话说,人多好办事!在程序里也是这样,如果是同一个应用程序需要并行处理多件任务,那就可以创建多条线程。但是人多了,往往会出现冲突,使得这个工作无法再进行下去了,(三个和尚没水喝啊!)这就是“死锁”。

死锁,举个形象的例子,就像3(A、B、C)个人在玩3个球(1、2、3),规则很简单:每个人都必须先拿到自己左手边的球,才能拿自己右边的球,两手都有球之后,才能把球都放下。

这个游戏看起来似乎可以永远进行下去,但是若干局之后,如果三个人刚好都只拿到左手边的球,都等着那右手边的球,但是因为谁都不能放手,那么这三个人(线程)都将陷入无尽的等待中了,这就是传说中的“死锁”。

下面就用Java举例,例子中已经创建了3个boolean型的静态变量ball1、ball2、ball3(初始值为FALSE),TRUE代表球被拿起,FALSE代表球仍放在地上,接下来就是3个线程类:

1   2 Class PlayerA extends Thread //A的线程   3 {  4 	Public void run() {  5 		While(TRUE){ //无限循环   6 			While(ball3==TRUE) {} //如果ball3已被拿起,则进入等待   7 			ball3=TRUE; //当ball3被放下后,立刻拿起   8 			While(ball1==TRUE) {} //如果ball1已被拿起,则进入等待   9 			ball1=TRUE; //拿起ball1  10 			System.out.println(“A已经拿到两球!”)//为了方便观察死锁现象  11 			ball1=FALSE;ball3=FALSE;//然后放下两球  12 			} 13       } 14 } 15  16  17 Class PlayerB extends Thread //B的线程  18 { 19 	Public void run() { 20 		While(TRUE) {
//无限循环 21 While(ball1==TRUE) {} //如果ball1已被拿起,则进入等待 22 ball1=TRUE; //当ball1被放下后,立刻拿起 23 While(ball2==TRUE) {} //如果ball2已被拿起,则进入等待 24 ball2=TRUE; //拿起ball2 25 System.out.println(“B已经拿到两球!”)//为了方便观察死锁现象 26 ball2=FALSE;ball1=FALSE;//然后放下两球 27 } 28 } 29 } 30 31 32 Class PlayerC extends Thread{ //C的线程 33 34 Public void run() { 35 While(TRUE){ //无限循环 36 While(ball2==TRUE) {} //如果ball2已被拿起,则进入等待 37 ball2=TRUE; //当ball2被放下后,立刻拿起 38 While(ball3==TRUE) {} //如果ball3已被拿起,则进入等待 39 ball3=TRUE; //拿起ball1 40 System.out.println(“C已经拿到两球!”)//为了方便观察死锁现象 41 ball3=FALSE;ball2=FALSE;//然后放下两球 42 } 43 } 44 } 45

Main()函数略,运行这个程序,你会看到有若干行打印信息后,就不再有输出,那么就说明它“死锁”了。

那么我们如何来消除“死锁”呢?首先,让我们来看看产生“死锁”的必要条件:
1. 互斥,就是说多个线程不能同时使用同一资源,比如,当线程A使用该资源时,B线程只能等待A释放后才能使用;
2. 占有等待,就是某线程必须同时拥有N个资源才能完成任务,否则它将占用已经拥有的资源直到拥有他所需的所有资源为止,就好像游戏中,必须两个球都拿到了,才能释放;
3. 非剥夺,就是说所有线程的优先级都相同,不能在别的线程没有释放资源的情况下,夺走其已占有的资源;
4. 循环等待,就是没有资源满足的线程无限期地等待。
(嘿嘿~操作系统的知识还没有忘啊!)
有的读者已经明白了,只要打破这这几个必要条件,就能打破“死锁”!那么先来看看互斥:
要打破这个条件,就是要让多个线程能共享资源,就相当于A和B能同时举起ball1一样,当然在这个例子里我们可以这样修改规则,但是在其它程序中就不一定能了,比如说一个“读”线程,一个“写”线程,它们都能操作同一文件。在这种情况下,我们就不能“又读又写”文件,否则有可能会读到脏数据!因此我们很少从这方面考虑。
占有等待
打破占有等待,只要当检测到自己所需的资源仍被别的线程占用,即释放自己已占有的资源(毫不利己,专门利人,呵呵~),或者在经过一段时间的等待后,还未得到所需资源,才释放,这都能打破占有等待。我们可以把While (TRUE)中的代码改一下(以A为例):

1 Outer:While(TRUE)//做标记   2 {  3    Int i=0;  4     While(ball3==TRUE) {} //如果ball3已被拿起,则进入等待   5      ball3=TRUE; //当ball3被放下后,立刻拿起   6     While(ball1==TRUE)  7     {  8      i++;  9  10 if(i==1000) //当计数达到1000后还未得到ball1,则放下ball3,并重新开始  11  12 { 13  14 ball3=FLASE; 15  16 break Outer; 17  18 } 19  20 } 21  22 ball1=TRUE; //拿起ball1  23  24 System.out.println(“A已经拿到两球!”)//为了方便观察死锁现象  25  26 ball1=FALSE;ball3=FALSE;//然后放下两球  27  28 } 29

其它两个线程也是如此,即可打破占有等待;

非剥夺
其实打破非剥夺,只要给线程制定一个优先级即可。比如例子中,我们设优先级从高到低为A、B、C,既当A需要ball3,而C正占有它,但是A的优先级比C高,那么C必须马上释放ball3。同理,A对B、B对C也是如此。代码修改如下:

1 Class PlayerA extends Thread //A的线程,优先级最高   2   3 {  4   5 Public void run()  6   7 {  8   9 While(TRUE) //无限循环  10  11 { 12  13 if(ball3==TRUE) //如果ball3已被C拿起  14  15 ball3=FALSE; //则“强迫”C放下ball3  16  17 ball3=TRUE; //当ball3被放下后,立刻拿起  18  19 if(ball1==TRUE) //如果ball1已被B拿起  20  21 ball1=FALSE; //则“强迫”B放下ball1  22  23 ball1=TRUE; //拿起ball1  24  25 System.out.println(“A已经拿到两球!”); 26  27 ball1=FALSE;ball3=FALSE;//然后放下两球  28  29 } 30  31 } 32  33 } 34  35  36 Class PlayerB extends Thread //B的线程,优先级第二  37  38 { 39  40 Public void run() 41  42 { 43  44 While(TRUE) //无限循环  45  46 { 47  48 While(ball1==TRUE) {} //如果ball1已被A拿起,则进入等待  49  50 ball1=TRUE; //当ball1被放下后,立刻拿起  51  52 if(ball2==TRUE) //如果ball1已被C拿起  53  54 ball2=FALSE; //则“强迫”C放下ball2  55  56 ball2=TRUE; //拿起ball2  57  58 System.out.println(“B已经拿到两球!”)//为了方便观察死锁现象  59  60 ball2=FALSE;ball1=FALSE;//然后放下两球  61  62 } 63  64 } 65  66 } 67  68  69 Class PlayerC extends Thread //C的线程,优先级最低  70  71 { 72  73 Public void run() 74  75 { 76  77 While(TRUE) //无限循环  78  79 { 80  81 While(ball2==TRUE) {} //如果ball2已被拿起,则进入等待  82  83 ball2=TRUE; //当ball2被放下后,立刻拿起  84  85 While(ball3==TRUE) {} //如果ball3已被拿起,则进入等待  86  87 ball3=TRUE; //拿起ball1  88  89 System.out.println(“C已经拿到两球!”)//为了方便观察死锁现象  90  91 ball3=FALSE;ball2=FALSE;//然后放下两球  92  93 } 94  95 } 96  97 }

通过这样的修改我们就能打破“非剥夺”(唉~和这个社会一样,可怜的小C啊!)。

最后的循环等待的解决方法其实和占有等待是一样的,都是等待一段时间后释放资源。好了,希望通过这个例子能让读者对“死锁”有一定的认识。.

转载地址:http://bwwvx.baihongyu.com/

你可能感兴趣的文章
OSChina 周一乱弹 ——程序员跟产品经理撕逼必须掌握的套路
查看>>
Linux系统启动流程详解
查看>>
Magento(CE1.X)自带模块解析五
查看>>
Factory Method模式 (一)
查看>>
代码整洁之道-第9章-单元测试-读书笔记
查看>>
C++ ssd5 12 optional exercise2
查看>>
如何调用带返回值类型的函数
查看>>
linux网络编程涉及的函数
查看>>
数据表的相关操作
查看>>
POJ 2594 Treasure Exploration(最小可相交路径覆盖)题解
查看>>
数据挖掘十大经典算法
查看>>
ArcGIS API for Silverlight 调用GP服务加载等值线图层
查看>>
CentOS系统rsync文件同步 安装配置
查看>>
LogStash配置、使用(三)
查看>>
SpringMVC 学习笔记(二) @RequestMapping、@PathVariable等注解
查看>>
Chrome应用技巧之颜色拾取
查看>>
Linux之通配符
查看>>
ios中摄像头和图片调用
查看>>
Content Provider 基础 之URI
查看>>
管理表空间和数据文件——使用OMF方式管理表空间
查看>>