wait、notify、notifyAll

  这三个方法都是属于Object的,Java中的类默认继承Object,所以在任何方法中都可以直接调用wait(),notifyAll(),notify(),static方法也一样,new一个对象再调用。这三个方法必须是在获取到monitor锁的前提下使用,也就是使用ReentrantLock这类锁是不行的,只能是synchronized关键字内部,否则会出现IllegalMonitorStateException异常。

1、wait:

  作用就是进入阻塞状态,准确的说是Waiting状态,如果调用的wait(timeout),进入Timed-Waiting状态,并且会释放monitor锁。

调用wait()有四种被唤醒方式:

  1).notify

  2).notifyAll

  3).wait(timeout)

  4).interrupt()

  这几种方式我们都比较熟悉,无论你开发过程中有没有用过多线程,提一下interrupt(),因为无论是调用sleep、wait、join等进入阻塞状态下,都是可以通过抛出异常响应中断。

基本使用:证明wait()会释放monitor锁

public class ThreadClass {

    private Object object = new Object();

    public static void main(String[] args) throws InterruptedException{
ThreadClass threadClass = new ThreadClass();
Thread thread = new Thread(threadClass.new Thread1());
Thread thread1= new Thread(threadClass.new Thread2());
thread.start();
Thread.sleep(100);
thread1.start();
} class Thread1 implements Runnable{
@Override
public void run() {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "获取到lock");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "继续执行");
}
}
} class Thread2 implements Runnable{
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "调用了notify");
}
}
}
}

代码实现

结果:
Thread-0获取到lock
Thread-1调用了notify
Thread-0继续执行

  从结果看,先让Thread-0执行,然后获取Object的monitor锁,然后调用wait进入Waiting状态,Thread-1执行了notify,所以证明wait()释放monitor锁,否则Thread-1无法进入同步块。Thread-0被唤醒,进入Runnable状态,继而获得CPU使用权,直到执行完成。

wait只释放当前monitor锁

public class ThreadClass {

    private Object object1 = new Object();
private Object object2 = new Object(); public static void main(String[] args) throws InterruptedException{
ThreadClass threadClass = new ThreadClass();
Thread thread = new Thread(threadClass.new Thread1());
Thread thread1 = new Thread(threadClass.new Thread2());
thread.start();
Thread.sleep(1000);
thread1.start();
} class Thread1 implements Runnable{
@Override
public void run() {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "获取到object1 monitor lock");
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "获取到object2 monitor lock");
try {
System.out.println(Thread.currentThread().getName() + "释放object1 monitor lock");
object1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
} class Thread2 implements Runnable{
@Override
public void run() {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "获取到object1 monitor lock"); System.out.println(Thread.currentThread().getName() + "尝试获取到object2 monitor lock");
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "获取到object2 monitor lock");
}
}
}
}
}

验证wait只释放当前对象的monitor

结果:
Thread-0获取到object1 monitor lock
Thread-0获取到object2 monitor lock
Thread-0释放object1 monitor lock
Thread-1获取到object1 monitor lock
Thread-1尝试获取到object2 monitor lock

代码执行流程:

  ①.Thread-0获取object1、object2的monitor锁,但是只释放object1的锁

  ②.Thread-1在Thread-0是否object1的锁之后,获取到object1的锁,但是无法获取object2的锁

所以证明,wait只释放当前对象的monitor。

wait()工作原理

原理解释:

入口集 Entry Set,等待集Wait Set

1、新启动的线程,会进入Entry Set去竞争monitor锁

2、其中一个线程得到monitor锁,进入红色区域,成为owner

3、此时有两个可能realise,如果调用wait(),进入Wait Set

4、直到被notify/notifyAll唤醒,进入最下面一层

5、此时和第二步一样都要去竞争获取锁,就是Blocked状态

6、程序执行结束,退出

2、notify、notifyAll

  从上面的代码中看到notify可以唤醒Waiting状态下的Thread,进入Runnable状态,但不一定立刻获取monitor锁,首先要等待别的Thread释放monitor锁,然后通过CPU调度是否获取锁。

notify:通过jvm选择唤醒其中一个调用wait()进入Waiting状态下的线程。

notifyAll:唤醒所有调用wait()进入Waiting状态下的线程。

public class ThreadClass {

    private Object object = new Object();

    public static void main(String[] args) throws InterruptedException{
ThreadClass threadClass = new ThreadClass();
Thread thread = new Thread(threadClass.new Thread1());
Thread thread1 = new Thread(threadClass.new Thread1());
Thread thread2= new Thread(threadClass.new Thread2());
thread.start();
thread1.start();
//休眠100ms保证notifyAll()后执行
Thread.sleep(100);
thread2.start();
} class Thread1 implements Runnable{
@Override
public void run() {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "获取到lock");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "继续执行");
}
}
} class Thread2 implements Runnable{
@Override
public void run() {
synchronized (object) {
object.notifyAll();
System.out.println(Thread.currentThread().getName() + "调用了notify");
}
}
}
}

notifyAll代码测试

结果:
Thread-0获取到lock
Thread-1获取到lock
Thread-2调用了notify
Thread-1继续执行
Thread-0继续执行

代码执行流程:

①.Thread-0和Thread-1启动

②.Thread-0获取到lock,然后执行wait()释放锁

③.Thread-1先进入Blocked状态,等待Thread-0释放锁之后,执行代码,然后执行wait()

④.Thread-2启动,执行notifyAll(),将所有因为wait()进入等待的线程唤醒

⑤.Thread-0和Thread-1竞争monitor锁,然后Thread-1得到锁,执行完成,释放锁

⑥.Thread-0获得锁,执行完成,释放锁

  所以证明notifyAll()能够唤醒所有调用wait()进入Waiting状态的线程,如果改成notify(),只会有一个线程继续执行。

总结:

1、wait()、notify()、notifyAll()都需要先获取monitor锁才能执行,否则抛出异常

2、notify只能获取一个调用wait()进入Waiting状态的线程,由jvm决定唤醒哪个,无法提前预知

3、notifyAll唤醒全部调用wait()进入Waiting状态的线程

4、wait()、notify()、notifyAll()都属于Object

思考题:为什么wait()需要在Synchronized内部使用,而sleep()不需要?

  为了防止死锁、永久等待的发生,如果没有这个前提,Thread-0在执行代码的时候,如果线程上下文切换到Thread-1,Thread-1直接执行notify了,但是Thread-0还没执行wait(),然后切换回来Thread-0,执行wait()之后,就只能永久等待了。但是如果在Synchronized内部,如果没有释放锁,其他线程不能进入的,能够避免这种可能的发生。

  而sleep只会休眠一段时间,不会出现永久等待的可能。

思考题:为什么wait(),notify(),notifyAll()定义在Object类中,而sleep()却定义在Thread类中?

  因为wait(),notify(),notifyAll()是锁级别的操作,因为每个Java对象的对象头中都保存着锁的状态,所以锁是属于对象的。如果wait()属于Thread类,我们如果想要让一个线程拥有多个monitor锁,我想要灵活的操作是否是否某个锁,就几乎无法实现了。

思考题:wait()属于对象,能不能调用Thread.wait(),如果调用会怎么样?

  可以通过Thread调用wait(),但是不建议。因为Thread类执行代码逻辑退出的时候,会唤醒所有处于waiting状态的线程,也就是notifyAll()的作用,这样就可能出现逻辑上的错误,所以不建议使用。

2、sleep

作用:在期望的时间内执行,其他的时候不占用CPU资源,不释放锁,直到timeout。

 sleep不释放锁(Synchronized、lock):

public class ThreadClass implements Runnable{

    private final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException{
ThreadClass threadClass = new ThreadClass();
Thread thread1 = new Thread(threadClass);
Thread thread2 = new Thread(threadClass);
thread1.start();
thread2.start();
} @Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取到ReentrantLock");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} }

sleep测试不释放lock

结果:
Thread-0获取到ReentrantLock
Thread-0休眠结束
Thread-1获取到ReentrantLock
Thread-1休眠结束

  这个结果,需要你自己执行代码,才能更加直观,Thread-0休眠3s过程,Thread-1还是没有获取到锁,证明了sleep不释放lock相关锁,Synchronized的monitor锁,可以自己试验一下。

  sleep的过程中,会响应interrupt,并且清除状态,这点和wait(),join()等其他阻塞相同。关于代码演示等详细内容,请参考:

并发和多线程(二)--创建线程和Interrupt的详细使用

面试题:sleep和wait的异同

相同点:

  1、执行之后都会进入阻塞状态。

  2、都会通过抛出异常响应interrupt,并且清除中断状态。

不同点:

  1、wait需要在Synchronized内部获取到monitor锁后,才能执行,否则会抛出异常。sleep没有限制

  2、wait会释放monitor锁,sleep不会释放锁,无论是monitor锁还是lock锁。

  3、wait可以选择是否指定时间阻塞,sleep必须指定时间。

  4、wait属于对象,而sleep属于Thread。

3、join

  当新的线程join,需要等新线程执行完成,其他线程才能继续执行。例如:main函数中执行thread1.join(),就是main线程等待thread1执行完毕。

public class ThreadClass{

//    private final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException{
ThreadClass threadClass = new ThreadClass();
Thread1 thread1 = threadClass.new Thread1();
Thread2 thread2 = threadClass.new Thread2();
thread1.start();
thread2.start(); thread1.join();
thread2.join(); System.out.println("线程1和线程2执行完成,main函数继续执行");
} class Thread1 extends Thread{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
}
} class Thread2 extends Thread{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
}
}
}

join()基本使用

结果:
线程2执行完成
线程1执行完成
线程1和线程2执行完成,main函数继续执行

  从结果可以证明,main线程等待线程1和线程2执行完成,才继续执行的,因为这两个子线程都休眠了1s,结果还是先打印。

join中断:

public class ThreadClass{

    public static void main(String[] args){
Thread currentThread = Thread.currentThread(); Thread thread = new Thread(() -> {
try {
currentThread.interrupt();
TimeUnit.SECONDS.sleep(3);
System.out.println("子线程sleep结束");
} catch (InterruptedException e) {
System.out.println("子线程被中断");
e.printStackTrace();
}
});
thread.start();
System.out.println("等待子线程执行完毕");
try {
thread.join();
} catch (InterruptedException e) {
// thread.interrupt();
System.out.println(Thread.currentThread().getName()+"被中断了");
e.printStackTrace();
}
System.out.println("子线程执行完成");
}
}

join中断响应

结果:
等待子线程执行完毕
java.lang.InterruptedException
main被中断了
at java.lang.Object.wait(Native Method)
子线程执行完成
at java.lang.Thread.join(Thread.java:1245)
at java.lang.Thread.join(Thread.java:1319)
at com.diamondshine.Thread.ThreadClass.main(ThreadClass.java:33)
子线程sleep结束

  当子线程thread执行join(),主线程main进入阻塞等待,join的中断和wait与sleep导致阻塞的中断是不同的,join中断子线程,必须中断主线程main,根据我们打印结果确实实现了主线程中断,但是存在一个问题,主线程响应中断继续执行到结束,但是我们发现子线程这时候还没有执行完成,子线程并没有中断。需要将前面注释的thread.interrupt();重新加上实现中断传递,就可以让子线程在sleep状态下响应中断,可以自己执行以下。

思考题:join期间线程的状态

public static void main(String[] args){
Thread currentThread = Thread.currentThread(); Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(currentThread.getState());
} catch (InterruptedException e) {
System.out.println("子线程被中断");
e.printStackTrace();
}
});
thread.start();
System.out.println("等待子线程执行完毕");
try {
thread.join();
} catch (InterruptedException e) {
thread.interrupt();
System.out.println(Thread.currentThread().getName()+"被中断了");
e.printStackTrace();
}
System.out.println("子线程执行完成");
}

join期间线程的状态

结果:
WAITING
子线程执行完成

  currentThread是主线程main的线程引用,System.out.println(currentThread.getState());能够打印的时候,是子线程还没有执行完成,肯定也是join期间,根据结果,我们知道join期间线程的状态为waiting,而不是有些人理解的Blocked。或者通过debug将断点打在sleep下面,可以看到thread-0和main线程的状态

join的原理

  join底层还是调用的wait,但是我们没有调用notify为什么,为什么线程会被唤醒?原因上面讲不建议使用Thread.wait()已经讲了,因为Thread.wait()会在执行结束退出的时候,自动进行wait的唤醒,这部分代码在native方法中实现。

自动wait唤醒C++源码如下:

join的替代写法

public static void main(String[] args) throws InterruptedException{
Thread currentThread = Thread.currentThread(); Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(currentThread.getState());
} catch (InterruptedException e) {
System.out.println("子线程被中断");
e.printStackTrace();
}
});
thread.start();
// thread.join();
synchronized (thread) {
thread.wait();
}
System.out.println("子线程执行完成");
}

join的替代写法

  join本身几乎可以等价于thread.wait(),由于wait需要持有synchronized的monitor锁才能执行,所以外层添加同步块,将thread本身作为对象锁,这样可以实现同样的效果。

PS:一般来说,要尽量避免join这类底层方法的使用,因为会增大程序出错的概率,同样的功能,更推荐CountDownLatch或者CyclicBarrier。

4、yield:

  yield会释放CPU的使用权,但是还是处于Runnable状态,因为yield不会释放锁,也不会进入任何一种阻塞状态,只是把CPU使用权让给相同或者更高优先级的线程,但是它本身随时可能获得CPU使用权。但是存在一个问题,jvm不是一定遵循yield的功能,例如:CPU使用率很低,即使执行yield,可能还在运行,而且不同jvm也不一样。所以,日常开发使用的比较少。

PS:yield更多用于J.U.C中,如AQS,ConcurrentHashmap,StampedLock,FutureTask等。

并发和多线程(四)--wait、notify、notifyAll、sleep、join、yield使用详解的更多相关文章

  1. “全栈2019”Java多线程第八章:放弃执行权yield()方法详解

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

  2. “全栈2019”Java多线程第二十三章:活锁(Livelock)详解

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

  3. java高并发系列 - 第20天:JUC中的Executor框架详解2之ExecutorCompletionService

    这是java高并发系列第20篇文章. 本文内容 ExecutorCompletionService出现的背景 介绍CompletionService接口及常用的方法 介绍ExecutorComplet ...

  4. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

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

  5. “全栈2019”Java多线程第九章:判断线程是否存活isAlive()详解

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

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

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

  7. “全栈2019”Java多线程第五章:线程睡眠sleep()方法详解

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

  8. “全栈2019”Java第一百零四章:匿名内部类与外部成员互访详解

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

  9. Java多线程的wait(),notify(),notifyAll()

    在多线程的情况下.因为多个线程与存储空间共享相同的过程,同时带来的便利.它也带来了访问冲突这个严重的问题. Java语言提供了一种特殊的机制来解决这类冲突,避免同一数据对象由多个线程在同一时间访问. ...

随机推荐

  1. map、filter、forEach、reduce数组方法的封装

    1.map方法的封装 ​Array.prototype.mapAlley = function(callback){    //获取调用mapAlley这个方法的数组    let arr = thi ...

  2. MyBatis mappers元素标签及其属性、配置

    mappers:映射器,以最佳的方式是告诉 MyBatis 到哪里去找映射文件. <!-- 使用相对于类路径的资源引用,要满足一个条件:1.即映射文件只要放在类路径下,就可以根据相对路径找到,放 ...

  3. storm0.91集群部署

    事先配置2台服务器配置好zookeeper,在配置文件中用zookeeper管理集群,配置文件如下 配置文件/conf/storm.yaml supervisor.slots.ports: 对于每个S ...

  4. js 关闭页面(Scripts may close only the windows that were opened by it.)

    传送http://blog.csdn.net/kuangfengbuyi/article/details/52052301 js关闭当前页面,当该页面不是其他页面打开的,而是直接输入url, 直接用w ...

  5. shell 一些命令(转)

    shell 一些命令(转) https://www.cnblogs.com/amei0/p/8041989.html 参考文档 http://man.linuxde.net/ 一.awk 求和 awk ...

  6. python中字典排序

    一.Python的排序 1.reversed() 这个很好理解,reversed英文意思就是:adj. 颠倒的:相反的:(判决等)撤销的 print list(reversed(['dream','a ...

  7. Reading books /// Prim+BFS oj21633

    题目大意: 输入 N,M 接下来1-N行输入读该书的用时time[i] 接下来1-M行输入a,b  表示a和b是similar的 若a读过则读b用时为 time[b]/2 ,若b读过则读a用时为 ti ...

  8. 百度开平台BAE搭建网站

    百度开平台BAE搭建网站 一.注册:在百度云注册账号,并且登陆 然后实名验证 二.开始搭建 三.部署项目:我们来把我们的项目提交上去 填写百度云的账号密码 四.删除:删除部署项目 以上就是百度开平台B ...

  9. Font Awesome (Mark)

    Font Awesome为您提供可缩放的矢量图标,您可以使用CSS所提供的所有特性对它们进行更改,包括:大小.颜色.阴影或者其它任何支持的效果. 一个字库,675个图标 仅一个Font Awesome ...

  10. Android Studio Gradle无法获取pom文件

    错误提示: Error:Execution failed for task ':app:lintVitalRelease'. > Could not resolve all artifacts ...