线程等待与唤醒主要是线程之间的通讯,java
分析了一下目前有三种,咱们接着分析;bash
先举个例子在说话,省略类的外层了:app
例1:测试
@Test public void test6() throws InterruptedException { Thread thread8 = new Thread(() -> { try { synchronized (this){ LOG.info("wait"); this.wait(); } LOG.info("wake up"); } catch (InterruptedException e) { e.printStackTrace(); } }); thread8.start(); Thread.sleep(3000); synchronized (this){ LOG.info("wake up thread "); this.notify(); Thread.sleep(2000); } }
例2:this
private final static String SIGN = "sign"; @Test public void test7() throws InterruptedException { Thread thread8 = new Thread(() -> { try { synchronized (SIGN){ LOG.info("wait"); SIGN.wait(); } LOG.info("wake up"); } catch (InterruptedException e) { e.printStackTrace(); } }); thread8.start(); Thread.sleep(3000); synchronized (SIGN){ LOG.info("wake up thread "); SIGN.notify(); Thread.sleep(2000); } }
日志输出:编码
2018-08-30 12:31:41.615 myAppName [Thread-0] INFO com.river.other.ThreadInterruptedTest - wait 2018-08-30 12:31:44.613 myAppName [main] INFO com.river.other.ThreadInterruptedTest - wake up thread 2018-08-30 12:31:46.613 myAppName [Thread-0] INFO com.river.other.ThreadInterruptedTest - wake up
线程Thread-0首先得到内置锁,调用wait()方法,使得当前线程处于 WAIT 状态,此时线程会释放锁;spa
主线程得到锁,调用监视对象的notify()方法唤醒Thread-0,主线程sleep 2s后释放锁,主线程结束运行;.net
此时Thread-0得到锁继续执行,打印go on ,执行完毕,结束运行;线程
咱们查看一下JDK中关于这几个方法的定义;翻译
使当前线程等待(WAIT)直到另外一个线程调用此对象的notify(),notifyAll()或者一段时间流逝以后;
当前线程必须拥有此对象的监视器;
该方法会使得当前线程(称之为T)将本身放置于对象的等待集中,而后放弃此对象上的全部同步要求;线程T对于线程调度变得不可用和休眠直到下面四件事情之一发生:
线程T被今后监视对象的等待集中移除时,对于线程调用从新变得可用,它接着会与其余在此监视对象上请求同步权利的线程以常规的方式竞争;一旦该线程得到了此对象的控制,全部在此对象上的同步要求都会被恢复,彻底恢复到调用wait()方法时的样子;线程T将会从wait()方法的调用中返回;
一个线程也会在不notify(),interrupted或者时间流逝后唤醒,一个所谓的假的唤醒;虽然他不多的发生在实践中;应用必须在测试中警戒假唤醒的发生的状况,若是条件不知足继续处于wait状态;换句话说,wait应该始终发生在循环中,以下:
synchronized (obj) { while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition }
解释一下上面的翻译:
监视对象就是锁对象,就是说在使用wait()一类方法和notify()一类方法时,必须先得到监视对象的锁,使用synchronized 关键字,synchronized 是一种JVM提供的内置锁,一般锁的对象有三种,详细点击java 锁分析 synchronized 和 Lock2.2小结;若在没有得到监视对象锁的状况下调用,则会抛java.lang.IllegalMonitorStateException异常;
当调用wait()方法时,当前线程会被放置到监视对象的等待集中,同时再也不持有锁,此时其余线程能够得到该监视对象的锁;线程进入WAIT状态,当被唤醒时,该线程将与其余等待相同监视对象锁的线程同样,等待CPU分配时间片,竞争锁,没有任何特权,该线程执行时,将彻底恢复到调用wait()方法时的状态,继续执行接下来的方法;
我的理解当RUNNING线程中监视对象调用wait()使得现场变为WAIT状态,此时线程处于不可用,不可用是针对CPU分配时间片来说的,jdk注释是这样讲的:
becomes disabled for thread scheduling purposes
也就是说不是线程调度对其无效,而是根本就是disabled,不在调度管理之中,CPU不会分配时间片给他;只有被唤醒才会被线程调度管理;
调用wait()方法处于WAIT的线程,会存在被假唤醒的状况,这种状况很罕见,但为了不这种状况发生致使应用发生未知的异常,建议将wait()方法处于循环体中,如上面的例子,使线程在不知足被唤醒的条件时始终处于WAIT状态,避免被假唤醒;
针对上面被唤醒的四种场景,咱们只重点介绍下第三种;其余三种都属于常规的唤醒WAIT中的线程,而第三种有一个异常 InterruptedException 须要处理;
package com.river.other; import lombok.extern.slf4j.Slf4j; import org.junit.Test; @Slf4j public class ObjectWaitTest { @Test public void test() throws InterruptedException { Thread thread1 = new Thread(() -> { synchronized (this) { try { log.info("wait..."); this.wait(); log.info("go on"); } catch (InterruptedException e) { log.error("error"); e.printStackTrace(); } log.info("go on..."); } }); thread1.start(); Thread.sleep(2000); log.info("interrupt"); Thread.sleep(500); thread1.interrupt(); } }
日志输出:
2018-08-30 17:17:38.304 myAppName [Thread-0] INFO com.river.other.ObjectWaitTest - wait... 2018-08-30 17:17:40.300 myAppName [main] INFO com.river.other.ObjectWaitTest - interrupt java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.river.other.ObjectWaitTest.lambda$test$0(ObjectWaitTest.java:15) at java.lang.Thread.run(Thread.java:745) 2018-08-30 17:17:40.800 myAppName [Thread-0] ERROR com.river.other.ObjectWaitTest - error 2018-08-30 17:17:40.801 myAppName [Thread-0] INFO com.river.other.ObjectWaitTest - go on...
经过日志分析可看到,处于WAIT状态的线程被调用interrupt()方法后,会当即抛出InterruptedException异常,该异常为检查异常,必须由编码处理的异常,catch异常以后线程会继续执行后面的操做;
忽然想到了sleep();
@Test public void test() throws InterruptedException { Thread thread1 = new Thread(() -> { synchronized (this) { try { log.info("wait..."); //this.wait(); Thread.sleep(10000); log.info("go on"); } catch (InterruptedException e) { log.error("error"); e.printStackTrace(); } log.info("go on..."); } }); thread1.start(); Thread.sleep(2000); log.info("interrupt"); Thread.sleep(500); thread1.interrupt(); }
日志输出:
2018-08-30 17:27:28.955 myAppName [Thread-0] INFO com.river.other.ObjectWaitTest - wait... 2018-08-30 17:27:30.954 myAppName [main] INFO com.river.other.ObjectWaitTest - interrupt 2018-08-30 17:27:31.454 myAppName [Thread-0] ERROR com.river.other.ObjectWaitTest - error java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.river.other.ObjectWaitTest.lambda$test$0(ObjectWaitTest.java:16) at java.lang.Thread.run(Thread.java:745) 2018-08-30 17:27:31.454 myAppName [Thread-0] INFO com.river.other.ObjectWaitTest - go on...
咱们看到日志输出与上面无异;
那么这里使用wait()和sleep()有什么区别呢?
wait() 用于必须得到监视对象锁以后执行,wait()方法调用后,会让出持有的锁供其余线程竞争得到;
一般用notify()唤醒,必须在得到同一监视对象锁在能够调用notify唤醒线程;
sleep() 使用没有限制,被调用后是当前线程让出CPU时间片,若在得到锁以后执行,则不会释放锁;
上面二者被interrupt打断后都会抛出InterruptedException异常须要处理;
notify用于唤醒当前监视对象等待集中WAIT的线程,若等待集中有多个线程,则调用notifyAll()将其所有唤醒,若此时调用notify(),则会唤醒任意一个线程(jdk中用词arbitrary),该方法不会释放持有的锁;
该处知识点已在上面有过对比介绍,不在重复了;
请在另外一篇文章java 锁分析 synchronized 和 Lock 3.1.4小节中查看;
总的来说,二和四主要是线程间通讯的,使用原理基本一致,都须要在得到监视对象的锁的状况下才能够调用方法,为什么如此呢?猜测既然是线程间通讯,确定是对同一资源或相关资源进行访问,线程之间彼此有影响,否则也不须要相互间保持通讯,既然有影响,那么就须要同步,因此须要串行执行操做;