java多线程之wait、notify、notifyAll

在java中,线程间通信可以使用wait、notify、notifyAll来进行控制。注意:这3个方法是Object的方法。

在调用一个对象的wait、notify、notifyAll方法时,必须持有该对象的锁。否则会报下面的错误:

1
2
3
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.tommy.core.test.threadtest.SubThread.run(Test1.java:58)

多个线程都持有同一个对象的时候,如果都要进入synchronized(obj){……}的内部,就必须拿到这个对象的锁,synchronized的机制保证了同一时间最多只能有1个线程拿到了对象的锁.

wait:线程自动释放其占有的对象锁,并等待notify
notify:唤醒一个正在wait当前对象锁的线程,并让它拿到对象锁
notifyAll:唤醒所有正在wait前对象锁的线程

notify和notifyAll的最主要的区别是:notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;notifyAll后并不是所有的线程都能马上往下执行,它们只是跳出了wait状态,接下来它们还会是竞争对象锁。

永远在循环(loop)里调用 wait 和 notify,不是在 If 语句。
原因参考:如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

下面通过一个IBM的多线程面试题来说明。
实现的功能是:主线程和子线程一次执行1次,共执行10次。

共享资源对象

1
2
3
4
class R {
// true:主线程运行标志;false:子线程运行标志。
boolean masterRunFlag = true;
}

主线程:

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
class MasterThread extends Thread {
private R r;
public MasterThread(R r) {
this.r = r;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (r) {
if (i == 10) {
break;
}
while (!r.masterRunFlag) {
try {
System.out.println("主线程等待执行.flag="+r.masterRunFlag);
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
r.masterRunFlag = false;
System.out.println("主线程第" + (i + 1) + "次执行。flag="+r.masterRunFlag);
i++;
r.notify();
}
}
System.out.println("主线程执行完毕。。。。");
}
}

子线程:

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
class SubThread extends Thread {
private R r;
public SubThread(R r) {
this.r = r;
}
@Override
public void run() {
int i = 0;
while (true) {
if (i == 10) {
break;
}
synchronized (r) {
while (r.masterRunFlag) {
try {
System.out.println("子线程等待执行。flag="+r.masterRunFlag);
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
r.masterRunFlag = true;
System.out.println("子线程第" + (i + 1) + "次执行完毕。flag="+r.masterRunFlag);
i++;
r.notify();
}
}
System.out.println("子线程执行完毕。。。。");
}
}

测试类:

1
2
3
4
5
6
7
8
9
public class Test1 {
public static void main(String[] args) {
R r = new R();
MasterThread mt = new MasterThread(r);
SubThread st = new SubThread(r);
mt.start();
st.start();
}
}

执行结果:

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
主线程第1次执行。flag=false
主线程等待执行.flag=false
子线程第1次执行完毕。flag=true
子线程等待执行。flag=true
主线程第2次执行。flag=false
主线程等待执行.flag=false
子线程第2次执行完毕。flag=true
子线程等待执行。flag=true
主线程第3次执行。flag=false
主线程等待执行.flag=false
子线程第3次执行完毕。flag=true
子线程等待执行。flag=true
主线程第4次执行。flag=false
主线程等待执行.flag=false
子线程第4次执行完毕。flag=true
子线程等待执行。flag=true
主线程第5次执行。flag=false
主线程等待执行.flag=false
子线程第5次执行完毕。flag=true
子线程等待执行。flag=true
主线程第6次执行。flag=false
主线程等待执行.flag=false
子线程第6次执行完毕。flag=true
子线程等待执行。flag=true
主线程第7次执行。flag=false
主线程等待执行.flag=false
子线程第7次执行完毕。flag=true
子线程等待执行。flag=true
主线程第8次执行。flag=false
主线程等待执行.flag=false
子线程第8次执行完毕。flag=true
子线程等待执行。flag=true
主线程第9次执行。flag=false
主线程等待执行.flag=false
子线程第9次执行完毕。flag=true
子线程等待执行。flag=true
主线程第10次执行。flag=false
主线程执行完毕。。。。
子线程第10次执行完毕。flag=true
子线程执行完毕。。。。

注意:主线程(生产者)和子线程(消费者)的执行次数必须相同。因为主线程和子线程的执行都依赖对方改变masterRunFlag的状态。
所以这里主线程执行10次,子线程也必须执行10次。

生产者、消费者模型,可以参考:https://www.jianshu.com/p/f7d4819b7b24

Donny wechat
欢迎关注我的个人公众号
打赏,是超越赞的一种表达。
Show comments from Gitment