Object中的wait、notify、notifyAll,可以用于线程间的通信,核心原理为借助于监视器的入口集与等待集逻辑
通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁(监视器)为中心的通信方法 
除了他们之外,还有用于线程调度、控制的方法,他们是sleep、yield、join方法,他们可以用于线程的协作,他们是围绕着线程的调度而来的 

sleep方法

有两个版本的sleep方法,看得出来,核心仍旧是native方法
非native方法只是进行了参数校验,接着仍旧是调用的native方法,这个情形与wait是类似的
接下来仔细看下,native版本的sleep
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。
注意:
sleep不会释放锁,不会释放锁,不会释放锁
可以理解为他进入监视器这个房间之后,在这房间里面睡着了
与wait类似的是,sleep也是可中断方法(从方法签名可以看得出来,可能抛出InterruptedException),也就是说如果一个线程正在sleep,如果另外的线程将他中断(调用interrupt方法),将会抛出异常,并且中断状态将会擦除
所以对于sleep方法,要么自己醒来,要么被中断后也会醒来
对于sleep始终有一个超时时间的设置,所以,尽管他是在监视器内睡着了,但是并不会导致死锁,因为他终究是要醒来的
 
如下,线程休眠500毫秒,主线程50毫秒打印一次状态
ps:sleep方法的调用结果为状态:TIMED_WAITING
借助于sleep方法,可以模拟线程的顺序执行
比如下面示例,两个阶段,第二个阶段将在第一个阶段执行之后才会执行
package test1;
import java.lang.Thread.State;
public class T16 {
public static void main(String[] args) {
//模拟执行任务的第一个阶段的执行
Thread stepOne = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" : 第一阶段任务开始执行");
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" : 第一阶段任务执行结束");
} catch (InterruptedException e) {
}
}, "firstStage");
stepOne.start();
//模拟任务第二个阶段的执行
Thread stepTwo = new Thread(() -> {
while (!State.TERMINATED.equals(stepOne.getState())) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : 我在等待第一阶段任务执行结束");
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName()+" : 第二阶段任务执行结束");
}, "secondStage");
stepTwo.start();
}
}
另外,你应该已经注意到sleep方法都有static修饰,既然是静态方法,在Thread中的惯例就是针对于:当前线程,当前线程,当前线程

yield方法

对于sleep或者wait方法,他们都将进入特定的状态,伴随着状态的切换,也就意味着等待某些条件的发生,才能够继续,比如条件满足,或者到时间等
但是yield方法不涉及这些事情,他针对的是时间片的划分与调度,所以对开发者来说只是临时让一下,让一下他又不会死,就只是再等等
yield方法将会暂停当前正在执行的线程对象,并执行其他线程,他始终都是RUNNABLE状态
不过要注意,可以认为yield只是一种建议性的,如果调用了yield方法,对CPU时间片的分配进行了“礼让”,他仍旧有可能继续获得时间片,并且继续执行
所以一次调用yield 并不一定会代表肯定会发生什么
借助于while循环以及yield方法,可以看得出来,也能一定程度上达到线程排序等待的效果
yield也是静态方法,所以,也是针对于当前线程,当前线程,当前线程。

join方法

三个版本的join方法
方法的实现过程,与wait也是非常类似,下面两个版本的方法一个调用join(0),一个参数校验后,调用join(millis),所以根本还是单参数版本的join方法
在方法深入介绍前先看个例子
一个线程,循环5次,每次sleep 1s,主线程中打印信息
从结果可以看到,主线程总是在线程执行之后,才会执行,也就是主线程在等待我们创建的这个线程结束,结束了之后才会继续进行
如果调整下顺序--->start 与 join的先后顺序,再次看下情况,可以发现顺序没有保障了
结论:
主线程main中调用启动线程(调用start),然后调用该线程的join方法,可以达到主线程等待工作线程运行结束才执行的效果,并且join要在start调用后
如何做到的?
从上面源代码可以看得出来,内部调用了wait方法,所以也能明白为啥join也会抛出InterruptedException了吧
主线程main中调用thread.join()方法,join方法相当于join(0),也就是
            while (isAlive()) {
                wait(0);
            }
而这个wait(0)就相当于是this.wait(0),this就是我们自己创建的那个线程thread,看看方法的签名是不是有一个synchronized
isAlive()也是this.isAlive(),也就是如果当前线程alive(已经启动,但是未终止),那么将持续等待,等待的临界资源就是我们创建的这个线程对象本身
所以这两行代码的含义就是:
该线程是否还存活?如果存活,调用join的那个线程将会在这个对象上进行等待(进入该线程对象的等待集)
也就是说调用一个线程的join方法,就是在这个线程是等待,这个线程对象就是我们的锁对象(不要疑惑,Object都可以作为锁,Thread实例对象怎么不可以?)
肯定大家很奇怪,既然是等待,wait又不会自己醒来,那不是出问题了吗?
其实线程结束后,会调用this.notifyAll,所以主线程main会被唤醒
 
如果传递的参数不为0,将会走到下面的分支,会wait指定时长,与上面的逻辑一致,只不过是有指定超时时长而已
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
 
手动版本的等待结束
只是将join方法换成了同步代码块,锁对象为那个线程的实例对象thread,调用他的wait方法
从结果上看,效果一样
(不过此处没有持续监测isAlive(),所以一旦主线程醒来,即使线程没有结束,也会继续,不能百分百确保main肯定等待线程结束)
不过要注意:注释中有说明,自己不要使用Thread类的实例对象作为锁对象,如果是现在这种场景,使用join即可
为什么?从我们目前来看,join方法就是以这个对象为锁,如果你自己在使用,又是wait又是notify(notifyAll)的,万一出现什么隐匿的问题咋办?
所以join方法的原理就是:将指定的Thread实例对象作为锁对象,在其上进行同步,只要那个线程还活着,那么就会持续等待(或者有限时长)
线程终止之后会调用自身this.notifyAll,以通知在其上等待的线程
简单说,只要他活着大家就都等着, 他死了会通知,所以效果就是在哪里调用了谁的join,哪里就要等待这个线程结束,才能继续
为什么要在start之后?
如上面所示,将join改造成同步代码块如下所示,如果这段同步代码在start方法之前
看下结果,没有等待指定线程结束,main主线程就结束了
因为如果还没有调用start方法,那么isAlive是false(已开始未结束),主线程根本就不会等待,所以继续执行,然后继续到下面的start,然后主线程结束
所以,为什么join方法一定要在start之前?
就是因为这个isAlive方法的校验,你没有start,isAlive就是false,就不会同步等待,所以必须要先start,然后才能join
小结:
对于join方法,有两个关键:
  • 调用的哪个对象的join?
  • 在哪里调用的?
换一个说法:
join的效果是:一个线程等待另一个线程(直到结束或者持续一段时间)才执行,那么谁等待谁?

在哪个线程调用,哪个线程就会等待;调用的哪个Thread对象,就会等待哪个线程结束;

状态图回顾

在回顾下之前状态一文中的切换图,又了解了这几个方法后,应该对状态切换有了更全面的认识

总结

对于yield方法,比较容易理解,只是简单地对于CPU时间片的“礼让”,除非循环yield,否则一次yield,可能下次该线程仍旧可能会抢占到CPU时间片,可能方法调用和不调用没差别
sleep是静态方法,针对当前线程,进入休眠状态,两个版本的sleep方法始终有时间参数,所以必然会在指定的时间内苏醒,他也不会释放锁,当然,sleep方法的调用非必须在同步方法(同步代码块)内
join是实例方法,表示等待谁,是用于线程顺序的调度方法,可以做到一个线程等待另外一个线程,join有三个版本,指定超时时间或者持续等待直到目标线程执行结束,join也无需在同步方法(同步代码块)内
sleep和join都是可中断方法,被其他线程中断时,都会抛出InterruptedException异常,并且会醒来
 
join方法底层依赖wait,我们对比下wait与sleep 
  • wait和sleep都会使线程进入阻塞状态,都是可中断方法,被中断后都会抛出异常
  • wait是Object的方法,sleep是Thread的方法
  • wait必须在同步中执行,sleep不需要(join底层依赖wait,但是不需要在同步中,因为join方法就是synchronized的)
  • wait会释放锁,sleep不会释放锁
  • wait(无超时设置的版本)会持续阻塞,必须等待唤醒,而sleep必然有超时,所以一定会自己醒来
  • wait 实例方法(Object),在对象上调用,表示在其上等待;sleep静态方法,当前线程

sleep、yield、join方法简介与用法 sleep与wait区别 多线程中篇(十五)的更多相关文章

  1. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  2. JavaSE之Long 详解 Long的方法简介以及用法

    基本功能 Long 类在对象中包装了基本类型 long 的值 每个 Long 类型的对象都包含一个 long 类型的字段 static long MAX_VALUE long 8个字节最大值2^63- ...

  3. java ---线程wait/notify/sleep/yield/join

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

  4. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  5. Object的wait/notify/notifyAll&&Thread的sleep/yield/join/holdsLock

    一.wait/notify/notifyAll都是Object类的实例方法 1.wait方法:阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒. wait等待其实是对象 ...

  6. 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

  7. 线程间的协作(wait/notify/sleep/yield/join)(五)

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

  8. Java线程中yield与join方法的区别

    长期以来,多线程问题颇为受到面试官的青睐.虽然我个人认为我们当中很少有人能真正获得机会开发复杂的多线程应用(在过去的七年中,我得到了一个机会),但是理解多线程对增加你的信心很有用.之前,我讨论了一个w ...

  9. sleep() wait() yield() join()用法与区别

    1.sleep()方法 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”.不推荐使用. sleep()使当前线程进入阻塞状态,在指定时间内不会执行. 2.wait()方法 在其他线程调用 ...

随机推荐

  1. 【状态表示】Bzoj1096 [SCOI2008] 着色方案

    Description 有n个木块排成一行,从左到右依次编号为1~n.你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块.所有油漆刚好足够涂满所有木块,即c1+c2+...+ck=n.相邻两个木 ...

  2. iview 菜单数据的转换,动态加载

    <template> <div class="changePassword"> <i-Menu ref="leftMenu" :t ...

  3. SQL Server 锁详解

    锁是一种防止在某对象执行动作的一个进程与已在该对象上执行的其他进行相冲突的机制.也就是说,如果有其他人在操作某个对象,那么你旧不能在该对象上进行操作.你能否执行操作取决于其他用户正在进行的操作. 通过 ...

  4. Helm学习笔记

    Helm学习笔记 Helm 是 Kubernetes 生态系统中的一个软件包管理工具.本文将介绍 Helm 中的相关概念和基本工作原理,并通过一个具体的示例学习如何使用 Helm 打包.分发.安装.升 ...

  5. Supervisor守护DotNet Core控制台程序

    Supervisor 相信对Linux系统很熟的都知道这个软件,基于Python写的一个守护进程软件.具体的介绍和使用我就不再赘述了. 使用asp.net core 部署在Linux常用的方法 我们可 ...

  6. eShopOnContainers 知多少[10]:部署到 K8S | AKS

    1. 引言 断断续续,感觉这个系列又要半途而废了.趁着假期,赶紧再更一篇,介绍下如何将eShopOnContainers部署到K8S上,进而实现大家常说的微服务上云. 2. 先了解下 Helm 读过我 ...

  7. Shim 与 Polyfill

    Shim: 用来向后兼容.比如 requestIdleCallback,为了在旧的环境中不报错,可以加 shim. 使用环境中现有的 api 来实现,不会引入额外的依赖或其他技术. Polyfill: ...

  8. len(x) 击败 x.len(),从内置函数看 Python 的设计思想

    内置函数是 Python 的一大特色,用极简的语法实现很多常用的操作. 它们预先定义在内置命名空间中,开箱即用,所见即所得.Python 被公认是一种新手友好型的语言,这种说法能够成立,内置函数在其中 ...

  9. Android Gradle 依赖配置:implementation & api

    背景: Android Gradle plugin 3.0开始(对应Gradle版本 4.1及以上),原有的依赖配置类型compile已经被废弃,开始使用implementation.api和anno ...

  10. Data Lake Analytics的Geospatial分析函数

    0. 简介 为满足部分客户在云上做Geometry数据的分析需求,阿里云Data Lake Analytics(以下简称:DLA)支持多种格式的地理空间数据处理函数,符合Open Geospatial ...