多线程知识梳理(4) synchronized 三部曲之等待 通知模型

1、概述

在前面两篇文章当中,咱们介绍了synchronized的基本使用和原理,可是在使用synchronized保证数据一致性的同时,咱们但愿可以让线程之间进行一些交互逻辑,也是咱们今天要介绍的等待/通知模型,那么就须要使用到wait/notifybash

2、等待/通知相关方法

2.1 方法说明

下面,咱们先介绍等待/通知机制的相关方法,首先要说明两点:ui

  • 这些都是Object定义的方法
  • 调用这些方法的前提条件是:该线程已经得到了Object对象所关联的锁,也就是说它们须要位于synchronized修饰的同步代码块中。

**(a) wait() ** 调用该方法的线程进入等待状态,并释放它所获取的对象锁,只有出现这两种状况之一,它才会从wait方法中返回,不然将会一直处于等待状态:spa

  • 其它线程经过notify / notifyAll方法通知该线程,而且该线程获取到了对象锁
  • 线程被中断

(b) wait(long) / wait(long, int)wait方法相同,差异是增长一种从wait方法返回的状况:等待的时间已经到了,而且获取到了对象锁。线程

(c) notify() 通知位于等待队列中的第一个线程,使其从wait()方法返回,而被通知的线程的继续执行须要等到它得到对象所为止。 须要注意,调用notify方法后,并不会马上释放它所持有的对象锁,这须要等到它执行完同步代码块为止。code

(d) notifyAll()notify()相似,可是它是通知全部在对象上等待的线程。对象

2.2 实现原理

经过上面的介绍,咱们能够看到,在整个等待/通知机制当中,线程被挂起时主要有如下三种状态:等待状态、超时等待状态、阻塞状态,这些状态都是经过synchroized所修饰的对象来实现的。队列

在前面咱们介绍synchronized原理的时候,曾经说过每一个对象都会和一个Monitor相关联,其实每一个Monitor又包含有两个队列:等待队列和同步队列,其中等待队列中存放是进入等待状态的线程,而同步队列中存放的是等待获取锁的线程。ip

下面,咱们经过一段简单的伪代码来当即两个线程的状态转换过程:同步

synchronized public void waitThread() {
    //执行a方法.
    wait();
    //执行b方法
}

synchronized public void notifyThread() {
    //执行c方法
    notify();
    //执行d方法
}
复制代码

咱们有AB两个线程,咱们模拟如下的一系列行为:it

(1) A 线程执行 waitThread 方法 此时因为对象锁没有被任何线程持有,所以,A线程成为对象锁的持有者:

(2) B 线程执行 notifyThread 方法B线程执行 notifyThread方法时,因为此时对象锁已经被 A线程持有,所以它被加入到同步队列中:
(3) A 线程执行 a 方法
**(4) A 线程执行 wait 方法 ** 当 A线程执行 wait方法后,它会释放对象锁,并加入到同步队列当中,而 B线程则成为对象锁新的持有者:
(5) B 线程执行 c 方法
(6) B 线程执行 notify 方法 此时会唤醒等待队列中 A线程,可是此时 B线程仍然持有对象锁,所以, A线程只能被加入到同步队列:
(7) B 线程执行 d 方法
(8) B 线程从 notifyThread 方法返回 此时 A线程从新获取到对象锁,所以它被从同步队列中取出,继续执行接下来的逻辑:
(9) A 线程执行 b 方法
(10) A 线程从 waitThread 方法中返回A线程从同步方法返回以后,那么会释放它所持有的锁

3、等待/通知的经典范式

对于等待/通知模型,咱们能够总结出它的经典范式,分别针对等待方和通知方。

3.1 等待方

等待方遵循以下的原则:

  • 获取对象的锁
  • 若是条件不知足,那么调用对象的wait方法,被通知后仍然须要检查条件
  • 条件知足则继续执行对应的逻辑

对应的伪代码为:

synchronized( 对象 ) {
    while( 条件不知足 ) {
        对象.wait();
    }
    对应的处理逻辑
}
复制代码

3.2 通知方

通知方遵循以下的原则:

  • 得到对象的锁
  • 改变条件
  • 通知全部等待在对象上的线程
synchronized( 对象 ) {
    改变条件;
    对象.notifyAll();
}
复制代码
相关文章
相关标签/搜索