死锁,线程通讯
1. 死锁定义
死锁是指两个或两个以上的执行单元(进程、线程或协程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
1.1 死锁产生原因
1.1.1 互斥条件
一个资源(锁)同一时间只能被一个线程占有,当这个资源被占用之后其它线程就只能等待。
1.1.2 不可剥夺条件
当一个资源被占用之后,如果不是拥有资源的线程释放,那么其它线程不能得到此资源。
1.1.3 请求并持有
当一个线程拥有了某个资源后,还不满足,又在请求其他资源。
1.1.4 环路等待条件
多个线程在请求资源的情况下,形成了环路链。
形成死锁,必须具备以上四个条件,缺一不可。
1.2 如何解决死锁
打破形成死锁的一个或者多个条件即可。
1.2.1 分析
- 互斥条件 -> 改变不了的
- 不可剥夺条件 -> 改变不了的
- 请求并持有条件 -> 人为控制 可以打破的 可以修改的
- 环路等待条件 -> 人为控制 可以被打破的 可以修改的
环路等待条件的破坏–> 使用顺序锁
2. 线程通讯
由于线程之间是抢占式执行的,因此线程之间的先后顺序是难以预知的。
但是实际开发中有时候我们希望合理的协调多个线程之间执行的先后顺序。
2.1 方法介绍
完成这个协调工作(线程通讯),主要涉及三个方法
注意: 都需要配合synchronized 使用 对象级别
原因:1.JVM 层面的规定。
2. 在运行的时候会判断这三个方法有没有配合 synchronized 使用,如果没有配合synchronized 使用会报错。防止多线程进行线程通讯的时候如果没有锁引发的线程混乱问题而设计的。
2.1.1 wait 使用
wait() / wait(long timeout) : 让当前线程进入等待状态
wait 执行流程:
使当前执行代码的线程进行等待(把线程放到等待队列中)
释放当前的锁
满足一定条件时被唤醒,重新尝试获取这个锁。
注意:wait 要搭配synchronized 使用,脱离synchronized 使用 wait 会直接抛出异常。
wait 结束等待条件:
其他线程调用该对象的notify 方法。
wait 等待超时
其它线程调用该等待线程的 interrupted 方法,导致 wait 抛出 interruptedException 异常。
public class WaitDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(()->{
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
t1.start();
}
}
2.1.2 notify 使用
notify() : 随机唤醒进入休眠状态的线程(随机)
notify() 调用之后并不是立马唤醒线程开始执行,而是要等待 notify 中的 synchronized 执行完(释放锁)之后才能真正的被唤醒起来开始执行
public class notifyDemo2 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(()->{
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
t1.start();
Thread t2 = new Thread(()->{
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
},"线程2");
t2.start();
Thread t3 = new Thread(()->{
System.out.println("线程3开始执行");
try {
synchronized (lock2) {
System.out.println("线程3调用wait方法");
// 无限期的等待状态
lock2.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t3.start();
// 唤醒lock 对象上休眠的线程
Thread t4 = new Thread(()->{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
System.out.println("线程4:开始执行,唤醒线程");
synchronized (lock){
lock.notify();
System.out.println("线程4:执行了唤醒操作");
}
},"线程4");
t4.start();
}
}
2.1.3 notiyAll 使用
notifyAll(): 唤醒所有处于休眠状态的线程。
2.2 注意事项
- wait /notify /notifyAll 必须要配合 synchronized 一起执行。
- wait /notify /notifyAll 进行 synchronized 加锁,一定要使用同一个对象进行加锁。
- 当调用了 wait /notify /notifyAll 之后,程序并不会立即恢复执行,而是尝试获取锁,只有得到锁之后才会继续执行。
- notifyAll 并不是唤醒所有的 wait 等待线程,而是唤醒当前对象处于 wait 等待的所有线程。
2.3 wait 有参和无参区别
不同点:
- wait(long timeout) : 当线程超过了设置时间之后,自动恢复执行;
wait() 无线等待状态 - 使用无参的 wait 方法,线程会进入 WAITING;
使用有参的wait方法,线程会进入TIMED_WAITING.
相同点: - 无论是有参的wait方法或者是无参的wait 方法都可以使当前线程进入休眠状态
- 无论是有参的wait 方法 或无参的wait 方法 都可以使用 notify 或 notifyAll 进行唤醒。
2.3.1 wait(0) 和sleep(0)区别
wait(0) : 无限期等待下去
sleep(0) :
import java.util.concurrent.TimeUnit;
public class WaitSleepDemo7 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(()->{
synchronized (lock) {
System.out.println("线程1:开始执行");
try {
lock.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:结束执行");
}
},"wait0");
t1.start();
Thread t2 = new Thread(()->{
System.out.println("线程2:开始执行");
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:执行结束");
},"sleep0");
t2.start();
}
}