等待&通知机制

等待/通知机制

什么是等待/通知机制?

举例说明,厨师和服务员之间的交互
1. 厨师做完一道菜的时间不确定,所以厨师将菜品放到”菜品传递台”上的时间也不确定;
2. 服务员取到菜的时间取决于厨师,所以服务员就处于等待状态
3. 服务员如何取到菜呢?又得取决于厨师,厨师将菜放在”菜品传递台”上,其实就相当于一种通知,这时服务员才可以拿到菜并交给就餐者。

如何实现等待/通知机制?

一句话总结:wait 使线程停止运行,而 notify 使停止的线程继续运行。

wait() 方法

  • Object 类的方法,作用:使当前执行代码的线程进行等待,直到接到通知被中断为止。
  • 执行之后,当前线程释放锁
  • 只能在同步方法或同步块中调用 wait()方法。原因:JDK 强制的,方法调用之前必须先获得该对象的对象级别锁,如果调用时没有持有适当的锁,则抛出 IllegalMonitorStateException 异常。

notify()/notifyAll() 方法

  • Object 类的方法,用来通知那些可能等待该对象的对象锁的其他线程
  • 在执行 notify() 方法后,当前线程不能马上释放该对象锁,wait 状态的线程也不能马上获取该对象锁,需要等到执行 notify() 方法的线程执行完(退出 synchronized 代码块后)。
  • 只能在同步方法或同步块中调用。原因如上。

线程状态切换

Runnable 状态 & Running 状态

Runnable:就绪状态,随时可能被 CPU 调度执行

Running:运行状态,线程获取 CPU 权限进行执行

  1. 创建一个新的线程对象后,调用 start() 方法,系统为此线程分配 CPU 资源,线程进入 Runnable 状态
  2. 如果线程抢占到 CPU 资源,此线程进入 Running 状态
Running 状态 -> Blocked 状态

blocked:阻塞状态,线程放弃了 CPU 使用权,暂时停止运行,直到线程进入就绪状态。分为三种:
1. 等待阻塞:调用 wait()方法,让线程等到某工作的完成
2. 同步阻塞:synchronized 获取对象锁失败(可能锁被占用)
3. 其他阻塞:调用 sleep() | join() | 发出 IO 请求时,线程进入阻塞。

  • 线程调用 sleep() 方法,主动放弃占用的处理器资源
  • 线程调用了阻塞式 IO 方法,在该方法返回前,该线程被阻塞
  • 线程试图获得一个同步监视器,但该监视器正被其他线程所持有
  • 线程调用 wait() 方法,等待某个通知
  • 程序调用了 suspend 方法将该线程挂起。(此方法容易死锁,避免使用)
Blocked 状态 -> Runnable 状态
  • 调用 sleep() 方法后,sleep()超时
  • 线程调用的阻塞 IO 已经返回,阻塞方法执行完毕
  • 线程成功获得了试图同步的监视器
  • 线程正在等到通知,其他线程发出了通知(notify() | notifyAll)
  • 处于挂起状态的线程调用了 resume() 恢复方法
Dead 状态

线程执行完了或者异常退出了 run() 方法,结束生命周期。

每个锁对象都有两个队列:
- 就绪队列:存储将要获得锁的线程,一个线程被唤醒后,进入就绪队列,等到 CPU 调度
- 阻塞队列:存储被阻塞的线程,一个线程被 wait() 后,进入阻塞队列,等待下一次被唤醒

wait/notify模式的注意事项

  • wait 释放锁,notify 不释放锁
  • 当线程处于 wait 状态时,使用 interrupt() 方法会出现 InterruptedException 异常
  • wait(long) 方法的作用:等待某一个时间内是否有线程对锁进行唤醒,如果超过时间则自动唤醒
  • 如果通知过早,则会打乱程序正常的运行逻辑。(wait 状态的线程不会被通知
  • wait 等待的条件发生变化,也容易造成程序逻辑的混乱

经典案例:生产者/消费者模式实现

实战:等待/通知之交叉备份

创建 20 个线程,其中 10 个线程是将数据备份到 A 数据中,另外 10 个线程是将数据备份到 B 数据库中,并且备份 A 数据库和 B 数据库是交叉进行的。

public class DBTools {

    // 确保备份 "★★★★★" 首先执行,然后与 "☆☆☆☆☆" 交替进行备份
    volatile private boolean prevIsA = false;

    synchronized public void backupA() {
        try {
            while (prevIsA) {
                wait();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("★★★★★");
            }
            prevIsA = true;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void backupB() {
        try {
            while (!prevIsA) {
                wait();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("☆☆☆☆☆");
            }
            prevIsA = false;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class BackupA extends Thread {
    private DBTools dbTools;

    public BackupA(DBTools dbTools) {
        super();
        this.dbTools = dbTools;
    }

    @Override
    public void run() {
        dbTools.backupA();
    }
}
public class BackupB extends Thread {
    private DBTools dbTools;

    public BackupB(DBTools dbTools) {
        super();
        this.dbTools = dbTools;
    }

    @Override
    public void run() {
        dbTools.backupB();
    }
}
public class Run {

    public static void main(String[] args) {

        DBTools dbTools = new DBTools();

        for (int i = 0; i < 20; i++) {
            BackupB output = new BackupB(dbTools);
            output.start();
            BackupA input = new BackupA(dbTools);
            input.start();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值