目的:就是让线程间具有互相发送信号通信的能力。

概述

核心:利用共享对象实现通信,这里的通信不是指传值,而是发送信号。
目的:就是让线程间具有互相发送信号通信的能力。
而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。
举个例子,一个线程B可能正在等待来自线程A的信号,这个信号告诉线程B数据已经处理好了。

线程通信

开发中不免会遇到,需要所有子线程执行完毕通知主线程处理某些逻辑的场景。
或者是 线程A 在执行到某个条件通知 线程B 执行某个操作。
在java中,比较典型的就是:等待通知机制。

等待通知机制

等待通知模式是 Java 中比较经典的线程通信方式。
两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。
这种方式,有三个参与者:

  1. 阻塞线程 wait()
  2. 唤醒线程 notify()
  3. monitor锁

看个最简单的例子:

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
public class TestWaitNotify4 {

/**
* A 1, B 1, B 2, B 3, A 2, A 3
*/
public static void main(String[] args) {
Object lock = new Object();

Thread A = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("A 1");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("A 2");
System.out.println("A 3");
}

}
});

Thread B = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");

lock.notify();
}
}
});

A.start();
B.start();
}
}

结果:

1
2
3
4
5
6
A 1
B 1
B 2
B 3
A 2
A 3

这个例子很简单,线程A 启动后,wait 自己,等待 线程B 唤醒自己。
这里 lock,就是一个 monitor锁,是不是奇怪,为什么需要一个 monitor锁,因为等待和唤醒必须是同一个锁,后面说。

线程通信方式

不同线程之间通过使用以下方法进行通信:

  1. wait(); 等待,该线程等待,并放弃执行权。
  2. notify(); 唤醒,唤醒正在等待中的其他线程。
  3. notifyAll(); 唤醒全部,推荐用这种
    以上三个方法必须是同步线程中才能使用,锁对象才能使用。
    只有同步才有锁的概念。

而上面三个方法是属于 Object 的方法,理由是:
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。
不可以对不同的锁中的线程进行唤醒。
也就是说等待唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

监视器对象

监视器对象,实际使用中,不止一种,不同场景有不同使用方式。
monitor锁是利用的对象的 monitor机制实现的,简单的说,对象的对象头存在隐藏字段,用来存储线程ID、锁标识、分代信息等,jvm 可以利用这个小存储进行线程状态的存储。

synchronized 方式

对象锁,就是创建一个中间对象,用来存储线程状态。
注间,这里方法使用 synchronized 进行修饰,synchronized 底层为 monitor锁。
使用 synchronized 和 synchronized(lock),这两个是等价的,锁的都是同一个 monitor锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// wait 和 notify 使用的是同一把锁
public Object lock = new Object();

public synchronized void notifySlef(){
synchronized(lock) {
lock.notify();
}
}

public void waitSlef(){
//注意,用的是 lock 这个对象来操作 wait
synchronized(lock) {
lock.wait();
}
}

this 方式

this 锁,锁的当前线程自己,所以只会等到自己执行完成才会出这个方法。
这里就有个问题,如果 this.wait(),谁来唤醒自己,因为 this 只能是自己持有,别的线程根本不可能拿到这个锁。

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
public class Test1 {

public static void main(String[] args) {
Test2 testWait = new Test2();
Thread t1 = new Thread(testWait);

t1.start();
}
}


class Test2 implements Runnable {

public synchronized void notifySelf(){
System.out.println("notify,thread: " + Thread.currentThread().getName());
this.notify();
}

public synchronized void waitSelf(){
try {
System.out.println("wait,thread: " + Thread.currentThread().getName());
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


@Override
public void run() {
System.out.println("current thread: " + Thread.currentThread().getName());
waitSelf();
notifySelf();
}
}

结果:
只 wait 住自己,没有办法再唤醒自己,卡在 wait 这一步,程序也不会退出。

1
2
current thread: Thread-0
wait,thread: Thread-0

在线程通信中,使用 this.wait() 如果没有设置超时时间,就会一直被阻塞,因为没有线程可以拿到 this 锁。

类锁方式

这种方式也是对象没,每个类都会对就有一个 Class 对象,实际上就是锁的该类的 Class 对象。

1
2
3
4
5
6
7
8
9
public static synchronized void notifyClass() {
Test.class.notify();
}

public void waitClass() {
synchronized (Test.class) {
Test.class.wait();
}
}

IllegalMonitorStateException 异常

异常代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class WaitTest {

public void testWait(){
System.out.println("Start-----");
try {
wait(1000); //没有一个锁对象,所以报错!!!
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End-------");
}

public static void main(String[] args) {
final WaitTest test = new WaitTest();
new Thread(new Runnable() {
@Override
public void run() {
test.testWait();
}
}).start();
}
}

错误提示

1
2
3
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
Thread-1
at java.lang.Object.wait(Native Method)

错误的主要原因为:
违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。

大白话就是

当前线程必须持有一个当前线程的锁,才能使用 wait。
当前线程并没有持有一个锁,就来调 wait 方法,直接抛异常。
要使用 wait 必须拥有该对象的锁!!!
详细说明在 wait() 方法的JDK注释中有详细说明。

正确写法

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
public class WaitTest {

public static void main(String[] args) {
final WaitTest test = new WaitTest();
new Thread(new Runnable() {
@Override
public void run() {
test.testWait();
}
}).start();
}


public synchronized void testWait(){
//增加Synchronized关键字
System.out.println("Start-----");
try {
wait(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End-------");
}

}

总结

waitnotify 一般在使用时,容易搞混的就是 IllegalMonitorStateException 异常。
主要是容易忽略需要加锁这件事,因为 waitnotify 是对象的自有方法,一般在使用时会想当然的就调用,而忽略了要先拿到锁的前提。