目的:就是让线程间具有互相发送信号通信的能力。
概述
核心:利用共享对象实现通信,这里的通信不是指传值,而是发送信号。 目的:就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。 举个例子,一个线程B可能正在等待来自线程A的信号,这个信号告诉线程B数据已经处理好了。
线程通信
开发中不免会遇到,需要所有子线程执行完毕通知主线程处理某些逻辑的场景。 或者是 线程A 在执行到某个条件通知 线程B 执行某个操作。 在java中,比较典型的就是:等待通知机制。
等待通知机制
等待通知模式是 Java 中比较经典的线程通信方式。 两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。 这种方式,有三个参与者:
- 阻塞线程 wait()
- 唤醒线程 notify()
- monitor锁
看个最简单的例子:
代码语言:javascript复制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();
}
}
结果:
代码语言:javascript复制A 1
B 1
B 2
B 3
A 2
A 3
这个例子很简单,线程A
启动后,wait 自己,等待 线程B
唤醒自己。
这里 lock
,就是一个 monitor锁,是不是奇怪,为什么需要一个 monitor锁,因为等待和唤醒必须是同一个锁,后面说。
线程通信方式
不同线程之间通过使用以下方法进行通信:
- wait(); 等待,该线程等待,并放弃执行权。
- notify(); 唤醒,唤醒正在等待中的其他线程。
- notifyAll(); 唤醒全部,推荐用这种 以上三个方法必须是同步线程中才能使用,锁对象才能使用。 只有同步才有锁的概念。
而上面三个方法是属于 Object 的方法,理由是:
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。
不可以对不同的锁中的线程进行唤醒。
也就是说等待
和唤醒
必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
监视器对象
监视器对象,实际使用中,不止一种,不同场景有不同使用方式。
monitor锁
是利用的对象的 monitor机制实现的,简单的说,对象的对象头存在隐藏字段,用来存储线程ID、锁标识、分代信息等,jvm 可以利用这个小存储进行线程状态的存储。
synchronized 方式
对象锁,就是创建一个中间对象,用来存储线程状态。 注间,这里方法使用 synchronized 进行修饰,synchronized 底层为 monitor锁。 使用 synchronized 和 synchronized(lock),这两个是等价的,锁的都是同一个 monitor锁。
代码语言:javascript复制// 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 只能是自己持有,别的线程根本不可能拿到这个锁。
代码语言:javascript复制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 这一步,程序也不会退出。
代码语言:javascript复制current thread: Thread-0
wait,thread: Thread-0
在线程通信中,使用 this.wait() 如果没有设置超时时间,就会一直被阻塞,因为没有线程可以拿到 this 锁。
类锁方式
这种方式也是对象没,每个类都会对就有一个 Class 对象,实际上就是锁的该类的 Class 对象。
代码语言:javascript复制public static synchronized void notifyClass() {
Test.class.notify();
}
public void waitClass() {
synchronized (Test.class) {
Test.class.wait();
}
}
IllegalMonitorStateException 异常
异常代码演示
代码语言:javascript复制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();
}
}
错误提示
代码语言:javascript复制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
注释中有详细说明。
正确写法
代码语言:javascript复制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-------");
}
}
总结
wait
和 notify
一般在使用时,容易搞混的就是 IllegalMonitorStateException
异常。
主要是容易忽略需要加锁这件事,因为 wait
和 notify
是对象的自有方法,一般在使用时会想当然的就调用,而忽略了要先拿到锁的前提。