Java并发编程艺术 - 多线程wait和notify

等待/通知机制

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而
执行后续操作。

上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的
关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

利用wait和notify进行交替执行

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class ThreadWaitAndNotify {

public static void main(String[] args) throws InterruptedException {

//对象锁标记
Object lock = new Object();

Runner runner1 = new Runner(lock, "runner1");
Runner runner2 = new Runner(lock, "runner2");
Thread thread1 = new Thread(runner1.runnable);
Thread thread2 = new Thread(runner2.runnable);

thread1.start();
Thread.sleep(100);
thread2.start();
}

@Slf4j
public static class Runner {
//全局自增
private static AtomicInteger num = new AtomicInteger(0);
private Object lock;
private String name;

public Runner(Object lock, String name) {
this.lock = lock;
this.name = name;
}

//运行
Runnable runnable = () -> {
//先获取锁,才能有锁状态变更
synchronized (lock) {
while (true) {
//首次:第一个线程为奇数,打印后等待;第二个线程为偶数,打印后通知第一个线程
num.incrementAndGet();
//等待唤醒
try {
if (num.get() % 2 == 1) {
log.info("name:{},num:{}", this.name, num.get());
//奇数等待偶数放行通知
lock.wait();
lock.notify();
} else if (num.get() % 2 == 0) {
log.info("name:{},num:{}", this.name, num.get());
//偶数放行,等待奇数执行
lock.notify();
lock.wait();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
}

交替执行结果

1
2
3
4
5
6
7
8
9
10
$Runner - name:runner1,num:1
$Runner - name:runner2,num:2
$Runner - name:runner1,num:3
$Runner - name:runner2,num:4
$Runner - name:runner1,num:5
$Runner - name:runner2,num:6
$Runner - name:runner1,num:7
$Runner - name:runner2,num:8
$Runner - name:runner1,num:9
$Runner - name:runner2,num:10

锁池和等待池

每个同步对象都有自己的锁池和等待池。

锁池

假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池

假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

notify和notifyAll适用场景

如上例,使用notify且2个runnner,可认为是一个生产者一个消费者的情况下,生产和消费有条不紊的运行,没有任何问题。

  • 线程调用了wait()方法,便会释放锁,并进入等待池中,不会参与锁的竞争
  • 调用notify()后,等待池中的某个线程(只会有一个)会进入该对象的锁池中参与锁的竞争,若竞争成功,获得锁,竞争失败,继续留在锁池中等待下一次锁的竞争。
  • 调用notifyAll()后,等待池中的所有线程都会进入该对象的锁池中参与锁的竞争。
  • notify适用于所有等待的线程都是对等的(或者说唤醒顺序对其执行任务无影响),或者适用于本来就只有一个等待线程的场景。
  • 只唤醒其它等待的消费者线程中的一个,如果改成notfiyAll的话就让其它线程被唤醒后立刻进入BLOCKED状态,增加了CPU的开销。
  • notfiyAll适用于等待的线程有不同的目标,可以并行执行的场景。这样才不至于使所有线程被唤醒后又进入到BLOCKED状态。
------ 本文结束------

本文标题:Java并发编程艺术 - 多线程wait和notify

文章作者:Perkins

发布时间:2019年09月09日

原始链接:https://perkins4j2.github.io/posts/48047/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。