小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Java多线程的等待通知机制
什么是等待通知机制?
在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if语句块中。
在多线程编程中,可能A线程的条件没有满足只是暂时的, 稍后其他的线程B可能会更新条件使得A线程的条件得到满足. 可以将A线程暂停,直到它的条件得到满足后再将A线程唤醒.它的伪代码:
atomics{ //原子操作
while( 条件不成立 ){
等待
}
当前线程被唤醒条件满足后,继续执行下面的操作
}
等待/通知机制的实现
Object类中的wait()方法可以使执行当前代码的线程等待,暂停执行,直到接到通知或被中断为止。
注意:
● wait()方法只能 在同步代码块中由锁对象调用。
● 调用wait()方法,当前线程会释放锁。
其伪代码如下:
//在调用wait()方法前获得对象的内部锁
synchronized( 锁对象 ){
while( 条件不成立 ){
//通过锁对象调用 wait()方法暂停线程,会释放锁对象
锁对象.wait();
}
//线程的条件满足了继续向下执行
}
Object类的notify()可以唤醒线程,该方法也必须在同步代码块中由锁对象调用. 没有使用锁对象调用 wait()/notify()会抛出IlegalMonitorStateExeption异常. 如果有多个等待的线程,notify()方法只能唤醒其中的一个. 在同步代码块中调用notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般将notify()方法放在同步代码块的最后. 它的伪代码如下:
synchronized( 锁对象 ){
//执行修改保护条件 的代码
//唤醒其他线程
锁对象.notify();
}
package com.wkcto.wait;
/**
* 需要通过notify()唤醒等待的线程
* 老崔
*/
public class Test03 {
public static void main(String[] args) throws InterruptedException {
String lock = "wkcto"; //定义一个字符串作为锁对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("线程1开始等待: " + System.currentTimeMillis());
try {
lock.wait(); //线程等待,会释放锁对象,当前线程转入blocked阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1结束等待:" + System.currentTimeMillis());
}
}
});
//定义第二个线程,在第二个线程中唤醒第一个线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//notify()方法也需要在同步代码块中,由锁对象调用
synchronized (lock){
System.out.println("线程2开始唤醒 : " + System.currentTimeMillis());
lock.notify(); //唤醒在lock锁对象上等待的某一个线程
System.out.println("线程2结束唤醒 : " + System.currentTimeMillis());
}
}
});
t1.start(); //开启t1线程,t1线程等待
Thread.sleep(3000); //main线程睡眠3秒,确保t1入睡
t2.start(); //t1线程开启3秒后,再开启t2线程唤醒t1线程
}
}
notify()方法后不会立即释放锁对象
package com.wkcto.wait;
import java.util.ArrayList;
import java.util.List;
/**
* notify()不会立即释放锁对象
* 老崔
*/
public class Test04 {
public static void main(String[] args) throws InterruptedException {
//定义一个List集合存储String数据
List<String> list = new ArrayList<>();
//定义第一个线程,当list集合中元素的数量不等于5时线程等待
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
if ( list.size() != 5 ){
System.out.println("线程1开始等待: " + System.currentTimeMillis());
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1被唤醒:" + System.currentTimeMillis());
}
}
}
});
//定义第二个线程,向list集合中添加元素
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (list){
for (int i = 0; i < 10; i++) {
list.add("data--" + i);
System.out.println("线程2添加了第" + (i+1) + "个数据");
//判断元素的数量是否满足唤醒线程1
if (list.size() == 5 ){
list.notify(); //唤醒 线程, 不会立即释放锁对象,需要等到当前同步代码块都执行完后才能释放锁对象
System.out.println("线程2已经发现唤醒通知");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
//为了确保t2在t1之后开启,即让t1线程先睡眠
Thread.sleep(500);
t2.start();
}
}