synchronized 实现等待-通知机制

zjun Lv4

Java中通过关键字synchronized来对对象加锁,同时配合wait(),notify(),notifyAll() 来实现等待-通知机制。但是在实际使用时,容易混淆wait()notifyAll()的含义,下面会详细讨论一下这两个方法的工作原理。

synchronized,wait,notifyAll的工作原理

Java中通过关键字synchronized来对对象加锁,确保在多线程的环境下,通过synchronized加锁的对象同一时间只有一个线程可以访问。它的工作原理可以简单概括为:Java中的每一个对象都可以成为一个监视器(Monitor),这个监视器由一个锁(lock),一个等待队列(waiting queue),和一个入口队列(entry queue)组成。

当一个线程取得了synchronized锁的访问,并在其内部调用wait()方法后,该线程会释放当前持有的对象的锁,然后该线程会被添加到该对象的等待队列中(waiting queue),并一直处于闲置状态,不会被调用执行。由于调用wait()方法首先会释放所持有的对象的锁,所以wait()只能在synchronized内部(也就是获得对象锁的前提下)执行,否则会抛出异常。

直到其它线程调用notify() notifyAll()方法时,调度器会从所有处于该对象等待队列(waiting queue)中的线程中取出任意线程,将其添加进入口队列(entry queue)中,然后入口队列中的线程会竞争对象的锁,得到锁的线程就可以继续执行。

notify()notifyAll()的区别是

  • notifyAll()会将等待队列中所有的线程都添加到入口队列中
  • notify() 会将等待队列中任意一个线程都添加到入口队列中

一个例子

下面我们用一个例子来说明synchronized配合wait(),notify(),notifyAll() 的使用方式。
假设有这样一个需求:

给定三个线程,分别命名为A、B、C,要求这三个线程按照顺序交替打印ABC,每个字母打印100次,最终输出结果为:
A
B
C
……

我们通过synchronized``wait(),notify(),notifyAll() 来实现,代码如下:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
public class PrintABC {  

private static final Object lock = new Object();
private static int state = 0;

public static void main(String[] args){
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i=0;i<100;i++){
synchronized (lock){
while(state%3!=0){
lock.wait();
}
System.out.println("A");
state++;
lock.notifyAll();
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i=0;i<100;i++){
synchronized (lock){
while(state%3!=1){
lock.wait();
}
System.out.println("B");
state++;
lock.notifyAll();
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i=0;i<100;i++){
synchronized (lock){
while(state%3!=2){
lock.wait();
}
System.out.println("C");
state++;
lock.notifyAll();
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});

threadA.start();
threadB.start();
threadC.start();
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
A
B
C
A
B
C
A
B
C
...

说明:

  • wait()方法是放在 while循环里,而不是if,这是因为存在一些特殊情况,会使得线程没有收到 notify 的信号时也能退出等待状态。
  • notify()方法唤起线程,会先唤起先进入等待队列的线程,而不是随机环形
  • notifyAll()方法唤起线程,默认会先唤起最后进入等待队列的线程,然后依次唤醒倒数第二个,倒数第三个线程,以此类推,即LIFO策略
  • notify()notifyAll()不会释放对象锁
  • wait/nofity 是通过JVM里的 park/unpark 机制来实现的

wait ()方法和 sleep()的区别

  1. sleep 是线程中的方法,但是 wait 是 Object 中的方法。
  2. sleep 方法不会释放 lock,但是 wait 会释放,而且会加入到等待队列中。
  3. sleep 方法不依赖于同步器 synchronized,但是 wait 需要依赖 synchronized 关键字。
  4. sleep 不需要被唤醒(休眠之后推出阻塞),但是 wait 需要(不指定时间需要被别人中断)。
  • 标题: synchronized 实现等待-通知机制
  • 作者: zjun
  • 创建于 : 2016-09-08 21:16:12
  • 更新于 : 2023-12-11 22:03:13
  • 链接: https://zjun.site/2016/09/46566b8b6e0d.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论