Java 多线程基础(六)线程等待与唤醒

遇到这样一个场景,当某线程里面的逻辑需要等待异步处理结果返回后才能继续执行。或者说想要把一个异步的操作封装成一个同步的过程。这里就用到了线程等待唤醒机制。

一、wait()、notify()、notifyAll() 等方法介绍

在 Object 中,定义了 wait()、notify() 和 notifyAll() 等接口。wait() 的作用是让当前线程进入等待状态,同时,wait() 也会让当前线程释放它所持有的锁。而 notify() 和 notifyAll() 的作用,则是唤醒当前对象上的等待线程;notify() 是唤醒单个线程,而 notifyAll() 是唤醒所有的线程。

Object类中关于等待/唤醒的API详细信息如下:

notify()                                       -- 唤醒在此对象监视器上等待的单个线程。
notifyAll()                                  -- 唤醒在此对象监视器上等待的所有线程。
wait()                                         -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)                    -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

二、wait() 和 notify() 示例

public class Demo02 {
public static void main(String[] args) {
Thread t1 = new MyThread("t1");
synchronized (t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName()+" call notify()");
notify(); // 唤醒当前的Demo02线程
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
// 运行结果
main start t1
main wait()
t1 call notify()
main continue

说明:

①、 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表Demo02中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
②、“主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
③、“主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
④、“线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
⑤、“线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。

具体过程图解

三、wait(long timeout) 和 notify()

public class Demo02 {
public static void main(String[] args) {
Thread t1 = new MyThread("t1"); synchronized(t1) {
try {
// 启动线程t1
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start(); // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3s延时;然后才被唤醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(); System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循环,不断运行。
while(true)
;
}
}
// 运行结果
main start t1
main call wait
t1 run // 3秒后输出 main continue
main continue 

说明:

如下图,说明了“主线程”和“线程t1”的流程。
①、注意,图中"主线程" 代表线程main。"线程t1" 代表MyThread中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
②、主线程main执行t1.start()启动“线程t1”。
③、主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
④、“线程t1”运行之后,进入了死循环,一直不断的运行。
⑤、超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。

 具体过程图解:

四、wait() 和 notifyAll()

public class Demo02 {
private static Object obj = new Object();
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(5000)");
Thread.sleep(5000); // 休眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();
} }
static class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " run ");
obj.wait();
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 运行结果
t1 run
t2 run
main sleep(5000)
t3 run
main notifyAll()
t3 continue
t2 continue
t1 continue

说明:

①、 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
②、主线程通过sleep(5000)休眠5秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
③、主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!

具体过程图解

五、 为什么notify(), wait()等函数定义在Object中,而不是Thread中

Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

Java 多线程基础(六)线程等待与唤醒的更多相关文章

  1. Java多线程5:线程等待与唤醒

    原文:http://www.cnblogs.com/skywang12345/p/3479224.html wait(),notify(), notifyAll()等方法介绍在Object.java中 ...

  2. java多线程系列(六)---线程池原理及其使用

    线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...

  3. Java多线程核心技术(六)线程组与线程异常

    本文应注重掌握如下知识点: 线程组的使用 如何切换线程状态 SimpleDataFormat 类与多线程的解决办法 如何处理线程的异常 1.线程的状态 线程对象在不同运行时期有不同的状态,状态信息就处 ...

  4. Java多线程基础知识总结

    2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...

  5. java 多线程—— 线程等待与唤醒

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  6. java - 线程等待与唤醒

    Java多线程系列--“基础篇”05之 线程等待与唤醒 概要 本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. w ...

  7. “全栈2019”Java多线程第六章:中断线程interrupt()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. Java 多线程基础(七)线程休眠 sleep

    Java 多线程基础(七)线程休眠 sleep 一.线程休眠 sleep sleep() 方法定义在Thread.java中,是 static 修饰的静态方法.sleep() 的作用是让当前线程休眠, ...

  9. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

随机推荐

  1. dom4j——使用dom4j生成xml

    使用org.dom4j.Element 创建xml /** * 生成Service.xml文件 * @param tran 交易对象 * @param filePath 文件夹路径 */ public ...

  2. C# 使用Word模板导出数据

    使用NPOI控件导出数据到Word模板中方式: 效果如下: Word模板: 运行结果: 实现如下: Student.cs using System; using System.Collections. ...

  3. 对于近似有序序列(即除掉少数K个元素后是有序序列且K<<n),试分析直接插入排序,冒牌排序和简单选择排序的时间复杂度

    学弟问的一道数据结构的题,关于一些排序算法的时间复杂度. 针对近似有序序列, ①当使用直接插入排序时,其基本操作为数组中元素的移动.最好情况下,待排序列有序,无需移动,此时时间复杂度为O(n), 当为 ...

  4. Less定义变量

    1. 定义: 使用 @ 符号来定义变量 ,在Less中开头是 @ 则是变量,关于变量的命名方法,大家可以参考js中命名的规则,毕竟是做前端的,有着统一的习惯有助于我们统一风格.个人推荐变量名的命名规则 ...

  5. android小Demo--七彩霓虹灯效果

    七彩霓虹灯效果,基于网上的小Demo进行修改. 在android项目values文件夹下创建文件colors.xml,配置七种颜色: <?xml version="1.0" ...

  6. 用python编写测试脚本

    def f(n): """ >>>f(1) 1用例 >>>f(2) 2用例 ...... >>>f(n) n用例 & ...

  7. QueryRunner的添加与查询操作

    Apache-DBUtils实现CRUD操作,commmons-dbutils是Apache组织提供的开源JDBC工具类, 封装了针对于数据库的增删改查操作,Class QueryRunner Tes ...

  8. 【Java Spring Cloud 实战之路】- 使用Nacos和网关中心的创建

    0. 前言 在上一节中,我们创建了一个项目架构,后续的项目都会在那个架构上做补充. 1. Nacos 1.1 简介 Nacos可以用来发现.配置和管理微服务.提供了一组简单易用的特性集,可以快速实现动 ...

  9. Java实现 LeetCode 761 特殊的二进制序列(括号问题)

    761. 特殊的二进制序列 特殊的二进制序列是具有以下两个性质的二进制序列: 0 的数量与 1 的数量相等. 二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量. 给定一个特殊的二进制序列 ...

  10. C# Winform 学习(四)

    目标 1.文本类 2.选择类 3.导航类 一.文本类控件 1.文本标签Label 1)命名:lbl开始 2)常用属性: Text.Visible 2.文本框TextBox 1)命名:txt开始 2)常 ...