java基础--26.死锁问题的出现和解决
Posted 大数据小小罗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java基础--26.死锁问题的出现和解决相关的知识,希望对你有一定的参考价值。
同步弊端:
- 效率低
- 如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码重现
死锁:
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象
举例:
中国人、美国人吃饭案例
正常情况:
中国人:筷子两支
美国人:刀和叉
现在:
中国人:筷子一支,刀一把
美国人:筷子一支,叉一把
产生死锁问题:
中国人拿着刀的同时等着美国人把另一只筷子给他,美国人拿着一支筷子的同时等着中国人把刀给他
同步代码块的嵌套案例–重现死锁现象
//定义锁对象
public class MyLock
public static final Object objA = new Object();
public static final Object objB = new Object();
//定义Thread类的run()方法
public class DeadLock extends Thread
//定义一个flag变量,用来标记此时持有的是哪个锁
private boolean flag;
public DeadLock(boolean flag)
this.flag = flag;
@Override
public void run()
if(flag)
synchronized (MyLock.objA)
System.out.println("if objA");
synchronized (MyLock.objB)
System.out.println("if objB");
else
synchronized (MyLock.objB)
System.out.println("if objB");
synchronized (MyLock.objA)
System.out.println("if objA");
//测试用例
public class DeadLockDemo
public static void main(String[] args)
DeadLock dl1 = new DeadLock(true);
DeadLock dl2 = new DeadLock(false);
dl1.start();
dl2.start();
两个线程一旦启动,则将陷入互相等待的循环之中,就成为了死锁
死锁问题的解决
解决方式1:线程间通信–通过构造方法共享数据
有一个Student类作为资源(锁)
public class Student
/*
* 默认权限修饰符,是为了让同一个包下的其他类也能访问到其成员变量
*/
String name;
int age;
boolean flag; //默认情况下false:没有数据,如果是true:说明有数据
模拟生产者-消费者模型:
创建两个线程对象,一个是设置 Student 属性的线程 SetThread ,另一个是获取 Student 属性的线程 GetThread
public class SetThread implements Runnable
private Student st ;
private int x=0;
public SetThread(Student stu)
this.st = stu;
@Override
public void run()
while(true)
synchronized (st)
//判断有没有数据
if(st.flag)
try
st.wait(); //如果有数据就等待消费者消费
catch (InterruptedException e)
e.printStackTrace();
if(x%2 == 0)
st.name = "紫霞仙子";
st.age=27;
else
st.name = "刘意";
st.age=30;
x++;
//修改标记
st.flag = true;
//唤醒线程
st.notify();
public class GetThread implements Runnable
private Student st ;
public GetThread(Student stu)
this.st = stu;
@Override
public void run()
while(true)
synchronized (st) //加锁,防止出现姓名--年龄不匹配的问题
if(!st.flag) //没有数据等待
try
st.wait();
catch (InterruptedException e)
e.printStackTrace();
System.out.println(st.name+"--------"+st.age);
//修改标记
st.flag = true;
//唤醒线程
st.notify();
测试用例:
public class StudentDemo
public static void main(String[] args)
Student stu = new Student();
SetThread st = new SetThread(stu);
GetThread gt = new GetThread(stu);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
运行结果:
...
紫霞仙子--------27
紫霞仙子--------27
紫霞仙子--------27
紫霞仙子--------27
紫霞仙子--------27
刘意--------30
刘意--------30
刘意--------30
刘意--------30
刘意--------30
...
发现结果并不是那么理想,同一组数据出现多次
出现这种状况的原因:
CPU的一点点时间片执行时间,足以让一个线程中的代码执行多次
解决方式2:改进1:通过构造方法共享数据,并使用 (等待–唤醒) 机制实现线程间通信
存在问题:
A:如果消费者先抢到了CPU的执行权,就会去消费数据,但是现在的数据是默认值。
这样没有意义,应该等到数据有意义再去消费
B:如果生产者先抢到CPU的执行权,就会去生产数据,但是生产完数据之后,还拥有执行权它会继续生产。
这是有问题的,应该等待消费者将数据消费掉才能继续生产。
等待唤醒机制
A:生产者—先看看是否有数据,有就等待;没有就生产,然后通知消费者进行消费
B:消费者—先看看是否有数据,有就消费,消费完通知生产者继续生产;没有就等待
Object类中提供了3个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
问:wait和notify方法为什么不定义在Thread类上而是Object类上呢?
这些方法的调用必须通过锁对象来调用,而我们刚才使用的锁对象是任意对象
所以,这些方法必须定义在Object类中。
解决方式3:改进2:私有化锁对象的成员变量,自己提供get和set方法供外界调用
方式2中,同样也存在不适用的情况:一旦Student类中的成员变量被private修饰符私有化,那么其他Thread类就无法直接访问到其成员变量了。
解决方法:
Student自己实现get和set方法,外部Thread类直接调用其方法进行数据交换即可。
public class Student
/*
* 一旦权限修饰符改为private,其他类就无法访问到
* 改进:
* 将set和get方法写在 Student类中
*/
String name;
int age;
boolean flag; //默认情况下没有数据,如果是true,说明有数据
//student的set和get过程(包括同步)都是由自己来操作,外界调用就行
public synchronized void set(String name, int age)
//如果有数据就等待
if(this.flag)
try
this.wait();
catch (InterruptedException e)
e.printStackTrace();
//设置数据
this.name = name;
this.age = age;
//修改标记并唤醒线程
this.flag = true;
this.notify();
public synchronized void get()
//如果没有数据则等待
if(!this.flag)
try
this.wait();
catch (InterruptedException e)
e.printStackTrace();
//获取数据
System.out.println(this.name +"---"+this.age);
//修改标记并唤醒线程
this.flag = false;
this.notify();
/**
* 生产者类
* @author llj
*
*/
public class SetThread implements Runnable
private Student st ;
private int x=0;
public SetThread(Student stu)
this.st = stu;
@Override
public void run()
while(true)
if(x%2 == 0)
st.set("紫霞仙子", 27) ;
else
st.set("刘意", 30) ;
x++;
/**
* 消费者类
* @author llj
*
*/
public class GetThread implements Runnable
private Student st ;
public GetThread(Student stu)
this.st = stu;
@Override
public void run()
while(true)
st.get();
测试用例:
public class StudentDemo
public static void main(String[] args)
Student stu = new Student();
//将student引用作为参数传递实现线程间通信
SetThread st = new SetThread(stu);
GetThread gt = new GetThread(stu);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
运行结果:
紫霞仙子---27
刘意---30
紫霞仙子---27
刘意---30
紫霞仙子---27
刘意---30
紫霞仙子---27
刘意---30
紫霞仙子---27
刘意---30
紫霞仙子---27
刘意---30
...
总结
A.姓名和年龄出现不匹配
原因:
线程运行的随机性
解决方法:
加锁,在一个线程对共享资源操作的时候,其他线程只能等待该锁释放
* 注意: A: 不同种类的线程都要加锁
B: 不同种类的线程加的锁都必须是同一把
B.执行结果重复出现
原因:
CPU的一点点时间片执行时间,足以让一个线程中的代码执行多次
CPU执行程序时的随机性
解决方法:
利用 等待 – 唤醒 机制,让两个线程交替执行
以上是关于java基础--26.死锁问题的出现和解决的主要内容,如果未能解决你的问题,请参考以下文章
Java并发基础使用“等待—通知”机制优化死锁中占用且等待解决方案
Java死锁排查和Java CPU 100% 排查的步骤整理
Java死锁排查和Java CPU 100% 排查的步骤整理(转)
JAVA笔记(20)--- 死锁;如何解决线程安全问题;守护线程;定时器;Callable 实现线程;wait ( ) 和 notify ( ) ;实现生产者和消费者模式;