前言
死锁不是一种正常的现象, 是在多线程程序中需要避免的
定义
多个进程在执行过程中,因争夺同类资源且资源分配不当而造成的一种互相等待的现象,若无外力作用,它们都将永远无法继续执行,这种状态称为死锁,这些处于等待状态的进程称为死锁进程。
死锁代码
也许大家现在看到死锁的定义还不是很清楚很懵死锁到底是什么,接下来带大家通过代码来了解死锁,重点都在代码中体现,都标注的很清晰,认真看过才会有收获。
ChopStick类
package 线程.死锁问题;
public class ChopStick {
//代表筷子的编号
private String num;//1号筷子
//构建chopstack方法
public ChopStick(String num){
this.num=num;
}
//提供公有方法,get方法方便对筷子属性进行使用
public String getNum() {
return num;
}
}
ZhuJia类
package 线程.死锁问题;
public class ZhuJia extends Thread{
private ChopStick left;//左筷子
private ChopStick right;//右筷子
private String name;//使用者
//创建构造方法
public ZhuJia(ChopStick left,ChopStick right,String name){
this.left=left;
this.right=right;
this.name=name;
}
public void run(){
synchronized (left){
System.out.println(this.name+"拿起左侧筷子"+left.getNum()+"筷子"+"准备拿右侧"+right.getNum()+"筷子");
synchronized (right){
System.out.println(this.name+"拿起右侧筷子"+right.getNum()+"筷子正吃饭");
}
}
}
}
测试类
package 线程.死锁问题;
public class Test {
public static void main(String[] args) {
//构建筷子编号
ChopStick c_1=new ChopStick("1号");
ChopStick c_2=new ChopStick("2号");
ChopStick c_3=new ChopStick("3号");
ChopStick c_4=new ChopStick("4号");
ChopStick c_5=new ChopStick("5号");
//构建五个哲学家线程
ZhuJia z_1 = new ZhuJia(c_1, c_5, "李彦宏");
ZhuJia z_2 = new ZhuJia(c_2, c_1, "马云");
ZhuJia z_3 = new ZhuJia(c_3, c_2, "马化腾");
ZhuJia z_4 = new ZhuJia(c_4, c_3, "任正非");
ZhuJia z_5 = new ZhuJia(c_5, c_4, "柳传志");
//启动线程
z_1.start();
z_2.start();
z_3.start();
z_4.start();
z_5.start()
}
}
结果输出有来两种清况
正常效果如下
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
李彦宏拿起右侧筷子5号筷子正吃饭
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
马云拿起右侧筷子1号筷子正吃饭
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
马化腾拿起右侧筷子2号筷子正吃饭
柳传志拿起右侧筷子4号筷子正吃饭
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
任正非拿起右侧筷子3号筷子正吃饭
死锁效果如下
多个进程在执行过程中,因争夺同类资源且资源分配不当而造成的一种互相等待的现象,大白话就是谁都不想让就造成这种死锁现象
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
解决方案:
1.让有筷子交差的科学家,设立时间差
2.避免锁的嵌套
3.设置标志位|
方案一 设立时间差
ChopStick类
package 线程.死锁解决方案一;
public class ChopStick {
//代表筷子的编号
private String num;//1号筷子
//构建chopstack方法
public ChopStick(String num){
this.num=num;
}
//提供公有方法,get方法方便对筷子属性进行使用
public String getNum() {
return num;
}
}
ZhuJia类
package 线程.死锁解决方案一;
public class ZhuJia extends Thread{
/*
* 1.让有筷子交差的科学家,设立时间差
* 可以理解成交通信号灯,按顺序行驶
* */
private ChopStick left;//左筷子
private ChopStick right;//右筷子
private String name;//使用者
private int time;//设立时间差
//创建构造方法
public ZhuJia(ChopStick left, ChopStick right, String name,int time){
this.left=left;
this.right=right;
this.name=name;
this.time=time;
}
public void run(){
synchronized (left){
System.out.println(this.name+"拿起左侧筷子"+left.getNum()+"筷子"+"准备拿右侧"+right.getNum()+"筷子");
synchronized (right){
System.out.println(this.name+"拿起右侧筷子"+right.getNum()+"筷子正吃饭");
}
}
}
}
测试类
package 线程.死锁解决方案一;
/**
* @author fengxun
* @version 1.0
* @date 2020/4/25 15:41
*/
public class Test {
public static void main(String[] args) {
//构建筷子编号
ChopStick c_1=new ChopStick("1号");
ChopStick c_2=new ChopStick("2号");
ChopStick c_3=new ChopStick("3号");
ChopStick c_4=new ChopStick("4号");
ChopStick c_5=new ChopStick("5号");
//构建五个哲学家线程
ZhuJia z_1 = new ZhuJia(c_1, c_5, "李彦宏",100);
//时间差数值根据两者不相邻可以相同,反之不相同
ZhuJia z_2 = new ZhuJia(c_2, c_1, "马云",200);
ZhuJia z_3 = new ZhuJia(c_3, c_2, "马化腾",300);
ZhuJia z_4 = new ZhuJia(c_4, c_3, "任正非",200);
ZhuJia z_5 = new ZhuJia(c_5, c_4, "柳传志",300);
//启动线程
z_1.start();
z_2.start();
z_3.start();
z_4.start();
z_5.start();
//每个人都吃得了饭
/* 李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
李彦宏拿起右侧筷子5号筷子正吃饭
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
马云拿起右侧筷子1号筷子正吃饭
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
马化腾拿起右侧筷子2号筷子正吃饭
柳传志拿起右侧筷子4号筷子正吃饭
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
任正非拿起右侧筷子3号筷子正吃饭*/
//就不会有一下这种清况每个人都不愿意放下手中的筷子,到最后都没吃上
/* 马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
*/
}
}
方案二 避免锁的嵌套
ChopStick类
package 线程.死锁解决方案二;
public class ChopStick {
//代表筷子的编号
private String num;//1号筷子
//构建chopstack方法
public ChopStick(String num){
this.num=num;
}
//提供公有方法,get方法方便对筷子属性进行使用
public String getNum() {
return num;
}
}
ZhuJia类
package 线程.死锁解决方案二;
public class ZhuJia extends Thread{
/*
* 2.避免锁的嵌套
* 可以把它想成嵌套for循环,外层执行一次,内层执行好多次直到结束才返回外层,就好比外层人员都拿着一支筷子,谁都不愿意放手,而内层循环走不出去就造成死锁现象
* */
private ChopStick left;//左筷子
private ChopStick right;//右筷子
private String name;//使用者
//创建构造方法
public ZhuJia(ChopStick left, ChopStick right, String name){
this.left=left;
this.right=right;
this.name=name;
}
public void run(){
//不嵌套得话当拿不到筷子时就回放下,这样就会使得每个都会吃上饭
synchronized (left) {
System.out.println(this.name + "拿起左侧筷子" + left.getNum() + "筷子" + "准备拿右侧" + right.getNum() + "筷子");
}
synchronized (right){
System.out.println(this.name+"拿起右侧筷子"+right.getNum()+"筷子正吃饭");
}
}
}
测试类
package 线程.死锁解决方案二;
public class Test {
public static void main(String[] args) {
//构建筷子编号
ChopStick c_1=new ChopStick("1号");
ChopStick c_2=new ChopStick("2号");
ChopStick c_3=new ChopStick("3号");
ChopStick c_4=new ChopStick("4号");
ChopStick c_5=new ChopStick("5号");
//构建五个哲学家线程
ZhuJia z_1 = new ZhuJia(c_1, c_5, "李彦宏");
ZhuJia z_2 = new ZhuJia(c_2, c_1, "马云");
ZhuJia z_3 = new ZhuJia(c_3, c_2, "马化腾");
ZhuJia z_4 = new ZhuJia(c_4, c_3, "任正非");
ZhuJia z_5 = new ZhuJia(c_5, c_4, "柳传志");
//启动线程
z_1.start();
z_2.start();
z_3.start();
z_4.start();
z_5.start();
//每个人都吃了饭
/* 李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
李彦宏拿起右侧筷子5号筷子正吃饭
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
马云拿起右侧筷子1号筷子正吃饭
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
马化腾拿起右侧筷子2号筷子正吃饭
柳传志拿起右侧筷子4号筷子正吃饭
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
任正非拿起右侧筷子3号筷子正吃饭*/
//先这种清况每个人都不愿意放下手中的筷子
/* 马云拿起左侧筷子2号筷子准备拿右侧1号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
*/
}
}
方案三 设置标志位
ChopStick类
package 线程.死锁解决方案三;
public class ChopStick {
//代表筷子的编号
private String num;//1号筷子
//判断筷子是否被占用
private boolean free = true;//筷子没有被占用
//构建chopstack方法
public ChopStick(String num){
this.num=num;
}
//提供公有方法,get方法方便对筷子属性进行使用
public String getNum() {
return num;
}
//拿起筷子
public boolean Takeup() {
//判断筷子是否占用 true(没有占用)
if (free) {
free = false;//表示占用
return true;//代表占用
} else {
return false;//代表没有占用
}
}
//放下筷子
public void Putdome(){
free=true;
}
}
ZhuJia类
package 线程.死锁解决方案三;
/**
* @author fengxun
* @version 1.0
* @date 2020/4/25 15:32
*/
public class ZhuJia extends Thread{
private ChopStick left;//左筷子
private ChopStick right;//右筷子
private String name;//使用者
//创建构造方法
public ZhuJia(ChopStick left, ChopStick right, String name){
this.left=left;
this.right=right;
this.name=name;
}
public void run(){
/* synchronized (left){
System.out.println(this.name+"拿起左侧筷子"+left.getNum()+"筷子"+"准备拿右侧"+right.getNum()+"筷子");
synchronized (right){
System.out.println(this.name+"拿起右侧筷子"+right.getNum()+"筷子正吃饭");
}
}*/
while (true) {
//拿到左筷子
if (left.Takeup()) {//true
System.out.println(this.name + "拿起左侧筷子" + left.getNum() + "筷子" + "准备拿右侧" + right.getNum() + "筷子");
if (right.Takeup()) {
System.out.println(this.name + "拿起右侧筷子" + right.getNum() + "筷子正吃饭");
//给人吃饭预留些时间,不可能刚拿起来筷子就吃完了,
try {
Thread.sleep(500);//给他500毫秒时间让他吃饭
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("放下左筷子" + left.getNum() + "放下右筷子" + right.getNum());//吃完饭放下筷子让其他人吃饭
left.Putdome();//左筷子放下
right.Putdome();//右筷子放下
} else {
//拿到左筷子了,但是遗憾右筷子没有拿到,需要犯下左筷子
left.Putdome();
}
}
//还有一种是左筷子没有拿到,让他等一等,喝些水再吃饭
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
package 线程.死锁解决方案三;
public class Test {
public static void main(String[] args) {
//构建筷子编号
ChopStick c_1=new ChopStick("1号");
ChopStick c_2=new ChopStick("2号");
ChopStick c_3=new ChopStick("3号");
ChopStick c_4=new ChopStick("4号");
ChopStick c_5=new ChopStick("5号");
//构建五个哲学家线程
ZhuJia z_1 = new ZhuJia(c_1, c_5, "李彦宏");
ZhuJia z_2 = new ZhuJia(c_2, c_1, "马云");
ZhuJia z_3 = new ZhuJia(c_3, c_2, "马化腾");
ZhuJia z_4 = new ZhuJia(c_4, c_3, "任正非");
ZhuJia z_5 = new ZhuJia(c_5, c_4, "柳传志");
//启动线程
z_1.start();
z_2.start();
z_3.start();
z_4.start();
z_5.start();
//通过以下结果可知每位客人都吃上饭
/*
* 李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
马云拿起右侧筷子1号筷子正吃饭
柳传志拿起右侧筷子4号筷子正吃饭
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
放下左筷子5号放下右筷子4号
放下左筷子2号放下右筷子1号
马云拿起左侧筷子2号筷子准备拿右侧1号筷子
马化腾拿起左侧筷子3号筷子准备拿右侧2号筷子
马化腾拿起右侧筷子2号筷子正吃饭
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
柳传志拿起左侧筷子5号筷子准备拿右侧4号筷子
任正非拿起左侧筷子4号筷子准备拿右侧3号筷子
放下左筷子3号放下右筷子2号
李彦宏拿起左侧筷子1号筷子准备拿右侧5号筷子
李彦宏拿起右侧筷子5号筷子正吃饭
* */
}
}
案例(面试提问)
案例描述:
现在有张三想要李四的画,李四想要张三的书。
张三对李四说了:“你给我画,我就把书给你”。
李四也对张三说了:“你给我书,我就把画给你”。
此时,张三在等着李四的答复,而李四也在等着张三的答复,那么这样下去的最终结果就是张三得不到李四的画,李四也得不到张三的书。这实际上就是死锁的概念。
代码如下:
LISi类
package 线程.互要书画;
public class LISi {
//说话方法
public void say(){
System.out.println("李四对李四张三说你把画给我,我就把书给你");
}
//获得的方法
public void get(){
System.out.println("李四得到张三的画");
}
}
ZhangSan类
package 线程.互要书画;
public class ZhangSan {
//说话方法
public void say(){
System.out.println("张三对李四说你把书给我,我就把画给你");
}
//获得的方法
public void get(){
System.out.println("张三得到李四的书");
}
}
MyThread类
package 线程.互要书画;
public class MyThread extends Thread {
//标识
boolean flag=true;//为true张三先开口
//声明张三李四注意用static修饰,保证张三李四唯一性(只开辟一处空间,要不然写两次)
public static ZhangSan zhangSan=new ZhangSan();
public static LISi liSi=new LISi();
@Override
public void run(){
if (flag) {
zhangSan.say();
zhangSan.get();
}else {
liSi.say();
liSi.get();
}
}
}
测试类
package 线程.互要书画;
public class Test {
public static void main(String[] args) {
//创建张三李四线程
MyThread myThread=new MyThread();
MyThread myThread1=new MyThread();
//
myThread.flag=true;//张三先开口
myThread1.flag=false;
//启动线程
myThread.start();
myThread1.start();
//实现了张三向李四借书,李四向张三借画过程
/* 张三对李四说你把书给我,我就把画给你
李四对李四张三说你把画给我,我就把书给你
张三得到李四的书
李四得到张三的画*/
}
}
总结
1、注意
死锁不是一种正常的现象, 是在多线程程序中需要避免的
2、产生死锁的原因主要是
因为系统资源不足。
进程运行推进的顺序不合适。
资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁
3、产生死锁的必要条件
(1) 互斥条件:一个资源每次只能被一 个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:线程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
想要代码可以到我资源处下载