一 什么是等待通知机制
在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if语句中。
在多线程中,可能A线程的条件没有满足只是暂时的,稍后其他的线程B可能会更新条件使得A线程的条件得到满足,可以将A线程暂停,直到他的条件得到满足后,我们在将A线程唤醒。他的伪代码:
atomics{
//原子操作
while(条件不成立){
//等待
}
//条件满足后,当前线程被唤醒,就继续执行下面的操作
}
二、 等待通知机制实现
Object类中的wait()方法,可以使当前代码的线程等待,暂停执行,当先线程转入blocked堵塞状态,直到接到通知或被中断为止。线程等待,释放锁对象,
注意:
- wait()方法只能在同步代码块中,由锁对象调用
- 调用wait()方法后,当前线程会释放锁
其伪代码:
//在调用wait()方法前要获取对象的内部锁
synchronized(锁对象){
while(条件不成立){
//通过锁对象来调用wait()方法暂停线程,释放锁对象
锁对象.wait();
}
//线程的条件满足了,继续向下执行
}
Object类的notify()可以唤醒线程,该方法也必须在同步代码块中,由锁对象调用,没有使用锁对象调用wait()/notify()会抛出异常: IlegalMonitortateExption如果有多个等待的线程,notify()方法只能唤醒其中的一个。 在同步代码块中调用notify()方法后,不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般将notify()方法放在同步代码块的最后。
它的伪代码:
synchronized(锁对象){
//修改保护条件的代码
//唤醒其他线程
锁对象.notify();
}
三、wait方法的基本使用
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* 演示wait方法和notify方法,需要放在同步代码块中否则会产生java.lang.IllegalMonitorStateException异常
*
* 任何对象都可以调用wait/notify,这连个方法是从Object类继承来的
* @date 2021/3/9 21:20
*/
public class Test01 {
public static void main(String[] args) {
try {
String test = "wkcto";//java.lang.IllegalMonitorStateException
test.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
四、 notify方法的简单实例
notify()方法后不会立即释放锁对象
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* 需要通过notify来唤醒等待的线程
* @date 2021/3/9 21:28
*/
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.dome.wait;
import java.util.ArrayList;
import java.util.List;
/**
* @author qb
* @version 1.0
* @Description
* notify()不会立即释放锁对象
* @date 2021/3/9 21:39
*/
public class Test04 {
public static void main(String[] args) {
//定义一个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("线程等待:"+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之后开启,
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
执行结果
五、interrupt中断线程的wait等待
当线程处于wait等待状态时,调用线程对象的interrupt方法会中断线程的等待状态,会产生InterruptedException异常
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* Interrupt()会中断线程的wait等待
* @date 2021/3/10 19:37
*/
public class Test05 {
public static void main(String[] args) {
SubThread t =new SubThread();
t.start();
try {
//主线程睡眠两秒,确保子线程处于wait等待状态
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
//定义常量作为锁对象
private static final Object lock = new Object();
static class SubThread extends Thread{
@Override
public void run() {
synchronized (lock){
try {
System.out.println("开始等待");
lock.wait();
System.out.println("结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("等待被中断");
}
}
}
}
}
运行结果
六、notify和notifyAll方法区别
notify一次只能唤醒一个线程,如果有多个等待线程,只能随机唤醒其中的某一个,如果想要唤醒所有等待线程,需要调用notifyAll方法
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* notify和notifyAll
* @date 2021/3/10 19:45
*/
public class Test06 {
public static void main(String[] args) {
Object lock = new Object(); //定义一个对象作为子线程的锁对象
SubThread t1 = new SubThread(lock);
SubThread t2 = new SubThread(lock);
SubThread t3 = new SubThread(lock);
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2秒后唤醒子线程,调用notify唤醒子线程
synchronized (lock){
//lock.notify(); //调用一次只能唤醒其中的一个线程,其他等待线程依然处于等待状态,对于处于等待状态的线程来说,错过了等待新号
//也成为新号丢失
lock.notifyAll();//唤醒所有等待线程
}
}
static class SubThread extends Thread{
private Object lock ; //定义实例对象,作为锁对象
public SubThread( Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName()+"--开始等待");
lock.wait();
System.out.println(Thread.currentThread().getName()+"---结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果
七、wait(login) 方法的使用
wait(long)带有long类型参数的wait等待,如果在参数指定的时间内,你没有被唤醒,超时后,会自动唤醒
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* wait(long)
* @date 2021/3/10 19:56
*/
public class Test07 {
public static void main(String[] args) {
final Object obj = new Object();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj){
try {
System.out.println("开始等待");
obj.wait(5000);
System.out.println("线程结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
}
运行结果
八、 通知过早
线程wait等待后,可以调用notify唤醒线程,如果notify唤醒的过早,在等待之前就调用了notify,可能会打乱程序正常的执行逻辑
调用start方法的顺序,不一定是线程开启的顺序。
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* notify通知过早
* @date 2021/3/10 20:00
*/
public class Test08 {
public static void main(String[] args) {
final Object lock = new Object(); //锁对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
try {
System.out.println("t1开始等待");
lock.wait();
System.out.println("t1等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("t2 notify");
lock.notify();
System.out.println("t2 结束notify");
}
}
});
t2.start();
t1.start();
//如果t2线程先唤醒,就不让t1线程等待
}
}
执行结果
解决通知过早
package com.dome.wait;
/**
* @author qb
* @version 1.0
* @Description
* notify通知过早
* @date 2021/3/10 20:00
*/
public class Test09 {
static boolean isFirst = true; //定义静态变量,作为是否第一个运行的标志
public static void main(String[] args) {
final Object lock = new Object(); //锁对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
while (isFirst) { //是第一个开启的,就等待
try {
System.out.println("t1开始等待");
lock.wait();
System.out.println("t1等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("t2 notify");
lock.notify();
System.out.println("t2 结束notify");
isFirst =false; //通知后将其改为false
}
}
});
t2.start();
t1.start();
//如果t2线程先唤醒,就不让t1线程等待
}
}
运行结果
九、wait条件放声变化
在使用wait/notify模式时,wait条件发生变化,也可能做成逻辑混乱
package com.dome.wait;
import java.util.ArrayList;
import java.util.List;
/**
* @author qb
* @version 1.0
* @Description
* wait条件发生变换
* 定义一个集合,通过一个线程向集合添加数据,另一个线程取数据,
* 如果集合没数据,就等待
* @date 2021/3/10 20:14
*/
public class Test10 {
public static void main(String[] args) {
//1.先定义添加数据的线程对象
ThreadAdd threadAdd = new ThreadAdd();
//2.定义取数据的线程对象
ThreadGet threadGet = new ThreadGet();
threadGet.setName("threadGet 1");
//测试1:先开启添加数据线程,在开启取数据线程,大多数情况下会正常执行
// threadAdd.start();
// threadGet.start();
//测试2:先开启取数据,在开启添加数据
// threadGet.start();
// threadAdd.start();
//测试3:开启两个取数据线程,在开启添加线程
/**
* threadGet 1开始等待
* threadGet 2从集合中去了data后,集合中数据数量0
* threadGet 1结束等待
* Exception in thread "threadGet 1"
* threadGet取数据时,没有数据就等待
* threadAdd获得cpu执行权添加数据,把threadGet线程给唤醒
* 但是threadGet2获得了执行权,正常取数据,threadGet线程
* 获得cpu执行权之后,打印结束等待,再执行reomve方法,现在list集合已经没有数据
*
* 出现异常原因,向list添加一个数据,remove了两次
*
* 如何解决:
* 当等待的线程唤醒后,先判断list中有没有数据,吧threadGet中的if改为while
*
*/
ThreadGet threadGet2 = new ThreadGet();
threadGet2.setName("threadGet 2");
threadGet.start();
threadGet2.start();
threadAdd.start();
}
//定义list集合
static List list = new ArrayList<>();
//定义方法从集合中取数据
public static void subtract(){
synchronized (list) {
while (list.size() == 0) {
try {
System.out.println(Thread.currentThread().getName()+"开始等待");
list.wait();
System.out.println(Thread.currentThread().getName()+"结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object date = list.remove(0);
System.out.println(Thread.currentThread().getName()+"从集合中去了"+date+"后,集合中数据数量"
+list.size()
);
}
}
//定义方法向集合中添加数据后,通知等待的线程取数据
public static void add(){
synchronized (list){
list.add("data");
System.out.println(Thread.currentThread().getName()+"存储了1数据");
list.notifyAll();
}
}
//定义线程加数据
static class ThreadAdd extends Thread{
@Override
public void run() {
add();
}
}
//定义线程取数据
static class ThreadGet extends Thread{
@Override
public void run() {
subtract();
}
}
}
运行结果