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. 进程间的通信——pipe通信

    当进程创建管道文件后,其建立的子进程自动继承该文件. 管道通信分为命名管道和未命名管道,他们的区别是命名管道在当创建他的进程结束后,系统仍存有该文件 管道的命令格式为 pipe(fds) 其中 fds ...

  2. 组件-vue自定义方法传递

    组件样式 面包屑导航栏 js Vue.component('bannerOne', { created() { console.log(this.bigbackColor); }, props: { ...

  3. windows10安全及性能优化

    一.关闭一些服务. Google 更新服务 (gupdate) Google 更新服务 (gupdatem) HomeGroupListener HomeGroupProvider Xbox Live ...

  4. [JavaWeb基础] 029.OGNL表达式介绍

    1.OGNL概述 OGNL,全称为Object-Graph Navigation Language,它是一个功能强大的表达式语言,用来获取和设置Java对象的属性,它旨在提供一个更高的更抽象的层次来对 ...

  5. eatwhatApp开发实战(三)

    在实战二中我们在eatwhatApp上增加了“添加店铺的功能”.接下来,我们来将添加的店铺显示出来,这里我们用到控件--ListView. 先上演示图: 首先,我们先设置布局: <Relativ ...

  6. [计划任务 - Linux]三分钟学会cron

    cron——计划任务,是任务在约定的时间执行已经计划好的工作,是一个linux下的定时执行工具,可以在无需人工干预的情况下运行作业. 也就是说cron只适合于linux系统,用windows电脑的同学 ...

  7. [PHP学习教程 - 类库]002.FTP操作(FTP)

    引言:FTP是大家上传至站点服务器必须要使用的协议.现在常用的FTP客户端工具也很多,如:8uftp,FlashFXP,....但是使用客户端工具就无法真正与自动化联系起来.所以今天,我们为大家讲一下 ...

  8. 一、Redis 总结

    官网 Redis 介绍 Redis 是一个开源的.支持网络.可基于内存亦可持久化的日志型.Key-Value 数据库,并提供多种语言的 API. Redis 是一个 key-value 存储系统.为了 ...

  9. JavaScript (六) js的基本语法 - - - Math 及 Date对象、String对象、Array对象

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.Math 1.Math对象的案例 var result= Math.max(10,20,30,40) ...

  10. (Java实现) 洛谷 P1071 潜伏者

    题目描述 R国和 S国正陷入战火之中,双方都互派间谍,潜入对方内部,伺机行动.历尽艰险后,潜伏于 S国的 R 国间谍小 C终于摸清了 S 国军用密码的编码规则: 1. S 国军方内部欲发送的原信息经过 ...