多线程篇五——wait和notify

如笔者理解有误,欢迎交流指正⭐

线程的执行先后顺序难以预料【抢占式执行】,但是实际开发中我们会需要掌握当下线程的执行顺序.
这就是wait和notify的作用.【都是Object方法即随便定义一个对象豆可以使用wait和notify】

wait()方法

wait执行过程

1.释放当前的锁
2.让线程进入阻塞
3.当线程被唤醒的时候重新获取到锁

上代码

1
2
3
4
5
6
7
8
9
10
public class Demo15 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("waiting...");
object.wait();
System.out.println("end.");
}
}
}

运行发现wait一直处于等待状态

1
tips
wait搭配synchronized使用,synchronized加锁给对象头进行标记

wait结束等待的条件

其他线程调用该对象的notify方法
wait等待超时(timeout用来指定等待时间)
其他线程调用该等待线程的interrupted方法,抛出InterruptedException异常

wait和sleep的对比
共同点

让线程在一段时间里不执行.【wait用于线程间通信(生产者-消费者模型) sleep用于线程阻塞(延迟操作)】

不同点

1.wait()可通过notify()唤醒,sleep()通过Interrupt()唤醒
2.使用wait()时没有设置最大等待时间,而sleep()是在知道最大时限的情况下使用(通过异常唤醒,说明程序应该是出现特殊情况了).
3.wait()搭配synchronized使用,sleep()不用.
4.wait()是Object的方法,sleep()是Thread的静态方法.
5.wait()方法会释放锁,sleep()不会.

notify()方法

此时我们的notify()方法就担起了唤醒wait()方法的大任(唤醒等待的线程).
tips
notify()要在同步方法或同步块中调用.它会通知可能等待该对象对象锁的其他线程,使得它们重新获取对象锁.
如果有多个线程等待,有线程调度器随机挑选一个wait状态的线程[【无先来后到】
调用notify()方法后不会立即释放对象锁,要等到执行notify()方法的线程将程序执行完之后【退出同步代码块】才会释放对象锁.

notify唤醒线程过程

1.创建 WaitTask 类, 对应一个线程, run 内部循环调用 wait.
2.创建 NotifyTask 类, 对应另一个线程, 在 run 内部调用一次 notify 注意, WaitTask 和 NotifyTask 内部持有同一个 Object locker.
3.WaitTask 和 NotifyTask 要想配合 就需要搭配同一个 Object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Demo16 {
public static Object locker = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("t1 wait 之前");
try {
//t1执行到这,就会先立即释放锁,进入wait方法(释放锁+阻塞等待)
locker.wait();
System.out.println("t1 wait 之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread t2 = new Thread(() -> {
try {
//t2执行起来之后,先进行sleep(3000)(这个sleep操作就可以让t1先拿到锁)
//如果先notify虽然不会有副作用(不会出现异常之类的),但是wait就无法被唤醒,逻辑上有问题
Thread.sleep(3000);
//t2sleep结束之后,由于t1是wait状态,t2就能拿到锁
//接下来打印t2notify之前,执行notify操作,这个操作就能唤醒t1(此时t1就从WAITING状态恢复过来了)
synchronized (locker) {
System.out.println("t2 notify 之前");
locker.notify();
//但是由于t2此时还没有释放锁,WAITING恢复之后,尝试获取锁,就可能出现一个小小的阻塞,这个阻塞是由锁竞争引起的
//t1目前处于BLOCKED状态,但是时间比较短,肉眼看不见
System.out.println("t2 notify 之后");
}
//t2释放锁之后,就可以继续执行t1
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}

notifyAll()方法

顾名思义哈 notifyAll()可以唤醒所有方法>
注意
唤醒的这些方法在等待(wait)返回时重新获取锁产生锁竞争,但实际这些锁是串行执行的【锁被哪个线程先获取是不确定的】

区别notify()和notifyAll()

1.唤醒一个/都唤醒
2.没有锁竞争/产生锁竞争
对比可发现notify()比notifyAll()好控制 发生错误的概率更低