Java线程详解:wait、notify、notifyAll、join
线程的概念
线程是进程的子任务,一个进程可以创建多个线程,线程不拥有系统资源,但是线程可以共享进程的资源,而线程自己也有一块独立的小块空间:包括堆栈,程序计数器和局部变量。
线程是CPU调度和分派的基本单位。在同一时刻cpu只能执行一段代码,或者说叫一段顺序执行流,也就是线程。cpu在不同的线程之间来回切换,因为cpu的运行速度非常高,看起来就像这些线程一起执行一样,这就是并发。
线程状态
从不同的角度,会有不同的结果。
说到线程的状态,网上的说法众说纷纭,有说5种的,有说6种的,还有说7种的。具体怎么回事呢,得看是从什么角度来看。、
从操作系统来看: 线程可以分为5个状态
- 新建状态(NEW)
线程的初始状态,还没有调用线程的start()方法 - 就绪状态(READY)
线程启动之后,就进入就绪状态,等待cpu分配时间片执行。处于就绪状态的线程,只是说线程做好了cpu随时调用执行的准备 - 执行状态(RUNNING)
cpu开始调度执行处于就绪状态的线程时,线程就是执行状态了。要想线程进入执行状态,它必须先是就绪状态 - 阻塞状态(BLOCKED)
当处于运行状态的线程,由于某些原因暂时放弃cpu的使用权,则处于阻塞状态。直到其进入就绪状态,cpu才可以重新调度执行它。 - 终止状态(TERMINATED)
线程执行完成或异常退出,线程终止。
而Java中,线程则分了6种状态。
新建和终止状态跟上面一样,就绪和执行状态,Java线程将这两种合并为一种:RUNNABLE:运行状态,表示线程已经在虚拟机中执行了。而阻塞状态,Java将之细分了3个状态: BLOCKED,WAITING,TIMEED_WAITING。
在Thread.State枚举中列举了这6种状态:
- 新建状态(NEW)
Thread 创建之后的初始状态,还未调用start()方法,只是内存中的一个对象,操作系统并没有创建线程。 - 运行状态(RUNNABLE)
线程已经在java虚拟机中执行,可能cpu调度已经正式开始运行,也有可能等待cpu调度。 - 阻塞状态(BLOCKED)
在Java中,BLOCKED 状态跟内置锁synchronized(也叫监视器锁)关联,线程在等待获取其它线程持有的监视器锁进入同步代码块时,线程处于BLOCK状态。或者在线程调用wait方法之后等待重新进入同步代码块,也处于BLOCK状态。BLOCK只跟监视器锁有关,如果不需要进入同步代码块,而是因为其它的原因线程需要等待,不属于BLOCK. - 等待状态(WAITING )
一个线程由于调用了以下方法之一: Object.wait(),Thread.join(),LockSupport.park()而处于等待状态。
处于WAITING状态的线程在等待其它线程执行特定的行为。例如:
一个线程A调用了某个对象上的wait()方法,需要等待其它线程去执行对象上的notify()或者notifyAll()方法,线程A才会结束等待。 - 限时等待(TIMED_WAITING)
一个线程由于调用了以下方法之一: Thread.sleep(long),Object.wait(long),Thread.join(long),LockSupport.parkNanos(long),LockSupport.parkUtil(long) 而等待。从这个命名上也说明是限时等待,不会无限等待下去,特定时间之后即使它等待的事件没有发生,也会退出等待状态继续执行。
终止状态(TERMINATED)
线程已经终止,包括正常执行完成或者异常退出。
线程通信: wait, notify, notifyAll, join
上面在RUNNABLE到WAITING,TIMED_WAITING状态的转换过程中,提到了wait,notify等方法。
wait
该方法是Object的一个实例方法,
/ ** @throws IllegalMonitorStateException if the current thread is not* the owner of the object's monitor.* @throws InterruptedException if any thread interrupted the* current thread before or while the current thread* was waiting for a notification. The <i>interrupted* status</i> of the current thread is cleared when* this exception is thrown.* @see java.lang.Object#notify()* @see java.lang.Object#notifyAll()* /public final void wait() throws InterruptedException {wait(0);}
这也表明任何对象都可以调用wait方法:此时会释放对象的监视器锁,(监视器锁是内置锁的另一种称呼,两者是一样的),并且当前线程(标记为线程T)会保持休眠
直到发生一下四种情况之一:
- 其它线程调用该对象上的notify方法,并且当前线程正好被选中唤醒
- 其它线程调用该对象上notifyAll方法
- 其它线程中断了当前线程,当前线程的中断标志也会被清除
- 调用wait时传入了一个正数的等待时间,等待的时间过去
以上四种情况发生之后,线程T结束休眠,重新进入线程调度:它会按正常的方式与其他线程竞争锁。也就是说,休眠结束后,线程的同步状态跟调用wait前完全一样。
第3种情况中,wait方法会抛出InterruptedException异常,并且将线程的中断标志清除
第4种情况中,等待的时间过后如果线程在竞争中没有获得内置锁,后续代码也是无法执行的,超时时间达到仅仅是把线程从休眠状态中移出
调用wait方法的线程必须拥有对象的监视器锁,怎么拥有呢,有3种方式:
5. 调用对象的synchronized同步方法
6. 执行锁定在这个对象上的同步代码块
7. 对于Class对象,执行该类上的静态同步方法(static synchronized methodName)
这也跟Java内置锁 synchronized里的“获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法”说法一致。
如果线程没有获得这个对象的监视器锁呢,wait方法会抛出 IllegalMonitorStateException异常。
虚假唤醒
我们调用wait时,经常是需要判断某些条件的,例如当某某资源找不到时,调用wait方法。通常会这么写
synchronized(obj){
dosomething
if( condition not satisfy){obj.wait()
}
实际上并不推荐这么写,而是这样:
synchronized(obj){
dosomething
while( condition not satisfy){obj.wait()
}
就是因为虚假唤醒。一个线程也有可能在没有别的线程调用notify,nofifyAll,也没有中断,等待的时间也没有到的时候被唤醒,这叫做”虚假唤醒“,虽然在实践中很少出现,但是应用也要预防这种情况的发生。
notify 和 notifyAll
这两个方法也是Object的实例方法:
/** @throws IllegalMonitorStateException if the current thread is not* the owner of this object's monitor.* @see java.lang.Object#notifyAll()* @see java.lang.Object#wait()*/public final native void notify();/* @throws IllegalMonitorStateException if the current thread is not* the owner of this object's monitor.* @see java.lang.Object#notify()* @see java.lang.Object#wait()*/public final native void notifyAll();
notify()方法唤醒一个正在该对象监视器锁的线程(调用了wait方法的线程),如果有多个线程在等待对象监视器锁,其中之一会被唤醒,具体唤醒哪个由java虚拟机决定。notifyAll()则会唤醒所有在该对象监视器上等待的线程,并且顺序是先进后出,最后一个进入休眠的线程会被首先唤醒
值得注意的是
调用notify(),notifyAll()时不会释放对象锁,而是notify所在的同步代码块结束之后才会释放锁,所以在调用notify,notifyAll方法之后,最好直接结束同步代码块。否则notify调用之后,锁不释放,被唤醒的线程也是在synchronized中的,获取不到对象锁也无法执行
notify,notifyAll 代码示例:
public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread1 sleep 2s");ThreadUtil.sleep(2000);System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");} catch (Exception e) {System.out.println("thread1 wait throw a exception");e.printStackTrace();}}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread2 sleep 2s");ThreadUtil.sleep(2000);System.out.println("thread2 call wait");try {service.wait();System.out.println("thread2 wait finish");} catch (Exception e) {System.out.println("thread2 wait throw a exception");e.printStackTrace();}}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread3 is running");service.notify();}}});thread1.start();thread2.start();ThreadUtil.sleep(2100); // 确保有一个线程调用了wait方法,如果没有线程调用wait方法而陷入等待,notify方法没有意义thread3.start();}执行结果:
thread1 sleep 2s
thread1 call wait
thread2 sleep 2s
thread2 call wait
thread3 is running
thread1 wait finish
thread1先执行,在调用wait之后释放对象锁进入等待,thread2获得对象锁,然后执行,也调用wait方法进入等待,thread3获得锁,调用notify方法唤醒某一个线程。thread1和thread2中一个会被唤醒,上面是thread1被唤醒:输出thread1 wait finish。也有可能是thread2被唤醒。notify只能唤醒一个线程,所以thread2的wait将会一直等待下去。
如果将thread3例的notify改为notifyAll。
thread2 sleep 2s
thread2 call wait
thread1 sleep 2s
thread1 call wait
thread3 is running
thread1 wait finish
thread2 wait finish
thread1和thread2都被唤醒了。
带时间参数的wait示例
public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {System.out.println("thread1 wait throw a exception");e.printStackTrace();}}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread2 call wait");try {service.wait(3000);System.out.println("thread2 wait finish");} catch (Exception e) {System.out.println("thread2 wait throw a exception");e.printStackTrace();}}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread3 is running");service.notify();}}});thread1.start();thread2.start();ThreadUtil.sleep(100);thread3.start();}
执行结果:
thread1 call wait
thread2 call wait
thread3 is running
thread1 wait finish
thread1 sleep 10s
// 这里等了10秒,而不是3秒
thread2 wait finish
thread2调用了wait(3000),超时时间设置为3秒。在thread3调用notify之后,唤醒的是thread1进程,然后thread1调用sleep(10s),(sleep函数不会影响内置锁)内置锁依然在thread1手上。在3秒中的时候,thread2从休眠状态醒来,但是它没有获取到内置锁,也只能等待。直到10秒后,thread1执行完毕,释放内置锁,thread2取得锁才接着执行。
wait中被中断示例
public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {System.out.println("thread1 wait throw a exception");System.out.println("exception Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());e.printStackTrace();}}}});Thread thread4 = new Thread(new Runnable() {@Overridepublic void run() {thread1.interrupt();}});thread1.start();ThreadUtil.sleep(100);thread4.start();}
结果:
Thread.currentThread().isInterrupted(): false
thread1 call wait
thread1 wait throw a exception
exception Thread.currentThread().isInterrupted(): false
java.lang.InterruptedExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at com.hz.MainTest$1.run(MainTest.java:85)at java.lang.Thread.run(Thread.java:748)
可以看到wait方法抛出了InterruptedException异常,并且线程的中断标志为false了。
join 方法
示例
join是Thread类的一个实例方法。
当前线程A调用另一个线程b的join()方法,会让当前线程A阻塞,直到线程b结束(包括正常结束或者异常退出)。
如果调用b.join(long)带时间参数的,则线程A会阻塞,但是如果时间达到b还没有结束,A也会结束阻塞。
public static void main(String[] args) throws Exception {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {e.printStackTrace();}}});thread1.start();thread1.join();System.out.println("main thread finish");执行结果:thread1 sleep 10s// 等待10秒main thread finish
主线程main会阻塞10秒,直到thread1结束,才输出 main thread finish
如果main中调用thread1.join(3000), 那主线程只会阻塞3秒。
为什么join 能达到这个效果呢。
我们以上文thread1.join()来分析一下源码
join方法源码
// 这是一个同步方法,先获得thread1的内置锁,thread1也是一个对象,也有内置锁
public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) { // 循环判断线程thread1是否还存活,存活则持续调用wait阻塞wait(0); // 这里的wait,是thread1.wait,而且是主线程调用的thread1.wait,阻塞的是主线程,thread1线程是正常执行的}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}
从上面的源码中,可以发现
- join(long millis)方法的签名中,加了synchronized关键字来保证线程安全,join(long millis)最终调用的是Object对象的wait()方法,让主线程等待。这里需要注意的是,synchronized关键字实现的隐式锁,锁的是this,即thread这个对象,上例中就是thread1对象(因为synchronized修饰的join(long millis)方法是一个成员方法,因此锁的是实例对象),我们是在main线程中,调用了thread这个对象的join()方法,所以调用wait()方法时,调用的是thread这个对象的wait()方法,所以是main线程进入到等待状态中。(调用的是thread这个对象的wait()方法,这一点很重要,因为后面唤醒main线程时,需要用thread这个对象的notify()或者notifyAll()方法)
- 既然是wait,那么必然有notify或者notifyAll。但是源码里并没有调用这两个方法,在哪里调用的呢,在jvm虚拟机中!
- Java里面的Thread类在JVM上对应的文件是thread.cpp。thread.cpp文件的路径是jdk-jdk8-b120/hotspot/src/share/vm/runtime/thread.cpp
在thread.cpp文件中定义了很多和线程相关的方法,Thread.java类中的native方法就是在thread.cpp中实现的。
join方法是当目标线程执行完成之后,主线程继续执行。我们猜测,notifyAll方法是在thread的run方法执行完之后调用的,做一些收尾工作。
在JVM中,当每个线程执行完成时,会调用到thread.cpp文件中JavaThread::exit(bool destroy_vm, ExitType exit_type)方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {assert(this == JavaThread::current(), "thread consistency check");// ...省略了很多代码// Notify waiters on thread object. This has to be done after exit() is called// on the thread (if the thread is the last thread in a daemon ThreadGroup the// group should have the destroyed bit set before waiters are notified).// 关键代码就在这一行,从方法名就可以推断出,它是线程在退出时,用来确保join()方法的相关逻辑的。而这里的this就是指的当前线程。// 从上面的英文注释也能看出,它是用来处理join()相关的逻辑的ensure_join(this);
可以看到,主要逻辑在ensure_join()方法中,接着找到ensure_join()方法的源码,源码如下
`static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), “java thread object must exist”);
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we’ve done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);// 核心代码在下面这一行
// notify_all()方法肯定就是用来唤醒。这里的thread对象就是我们demo中的线程thread1这个实例对象
lock.notify_all(thread);
既然这样,如果我提前将调用thread1的notifyAll()方法呢,那不就可以让主线程提前退出阻塞了么。于是我尝试了下面的代码
public static void main(String[] args) throws Exception {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {e.printStackTrace();}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {ThreadUtil.sleep(1000);synchronized (thread1) {try {System.out.println("call notify");thread1.notifyAll();System.out.println("after notify");} catch (Exception e) {e.printStackTrace();}}}});thread1.start();thread2.start();thread1.join();System.out.println("main thread finish");}结果:
Connected to the target VM, address: '127.0.0.1:58744', transport: 'socket'
thread1 sleep 10s
call notify
after notify
Disconnected from the target VM, address: '127.0.0.1:58744', transport: 'socket'
// 这句话依然是10秒钟后输出的
main thread finish
我在thread2中先停1秒钟,然后调用thread1.notifyAll(),是希望在1秒钟后,主线程能结束阻塞,继续执行。但是不起作用! why?
再来看源码里,有这一句
if (millis == 0) {while (isAlive()) { wait(0); }}
用的是while(isAlive()),而不是 if(isAlive()),thread1并没有结束,所以会再次调用wait阻塞。而且由于“虚假唤醒”的存在,调用wait时一般都是用while循环判断是否需要调用wait的,如下。
synchronized(obj){while (! condition){obj.wait()}
}
那我能不能把它改成if来验证呢,我本来想创建一个新的Thread子类,覆盖join方法,但是join方法是一个final方法,无法覆写,jdk也是考虑到这种情况吧
public final synchronized void join(long millis)
Java线程详解:wait、notify、notifyAll、join相关推荐
- java线程详解_Java线程详解
程序.进程.线程的概念程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 进程(process):是程序的一次执行过程,或是正在运行的一个程序.动 ...
- Java线程详解(深度好文)
Java线程:概念与原理 一.进程与线程 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间).进程不依赖于线程而独立存在,一个进程中可以启动多 ...
- Java线程详解(16)-条件变量
条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量.但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义. 这里的条件和 ...
- Java线程详解(13)-锁
Java线程:新特征-锁(上) 在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.lock ...
- Java线程详解(9)-并发协作
Java线程:并发协作-生产者消费者模型 对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应 ...
- Java线程详解(8)-线程的同步
Java线程:线程的同步-同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题, ...
- Java线程详解(6)-线程的交互
线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待.通知和通知所有线程. 一.线程交互的基础知识 SCJP所要求的线程交互知识点需要从java.lang.Object ...
- Java线程详解(7)-线程的调度
Java线程:线程的调度-休眠 Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率. 这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执 ...
- Java线程详解(18)-障碍器
Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了. 障碍器是多线程并 ...
最新文章
- JDBC操作数据库实例
- 在Spring Boot中实现通用Auth认证的几种方式
- 福利继续:赠书《Spring Cloud微服务-全栈技术与案例解析》
- Web:你知道我这十几年是怎么过来的吗?!
- 中专生计算机教案,[定稿]计算机基础教案中专V8.1(全文完整版)
- [1424] 金克拉与贪吃蛇的故事
- 【es】es 分布式一致性原理剖析 节点篇
- UFT开发代码实例:将Excel中的数据保存为数组
- [Java] 内部类总结
- ios怎么引入masonry_iOS Masonry的使用需要注意的地方
- 神泣单机服务器维护,神泣单机版
- 嘉楠科技多位高管退出,知情人称管理层夺权内斗
- ImageIO 本地读取,网络下载图片
- java判断微信号、手机、名字的正则表达
- 魅族手机突然显示无服务器,魅族Flyme6是悟空请来的?Bug竟然有这么多?
- 笔记:Bootstrap导航与router-link 不和谐
- 用Oracle PL/SQL 编程实现小数转分数的方法
- idea package自动生成_Java idea使用generator自动生成mapper | Rickytsang洛水寒
- 信息系统项目管理师试题精选(四)
- [BFS]营救营救铁达尼号-C++
热门文章
- AutoJs学习-实现度娘传情
- 计算机组装硬件有哪些,电脑有哪些硬件 组装电脑怎么选择电脑硬件【介绍】...
- python爬取b站弹幕并进行数据可视化
- cookie的组成与作用
- SSL是什么?关于SSL和TLS的常见问题
- excel跨多个表格求和_一个小小的米字符号,在Excel中起到关键作用,搭配任何函数使用...
- 计算机怎么将硬盘分小,硬盘分区工具,小编教你怎么给电脑硬盘分区
- 【简易广告机】利用树莓派制作一个简易的广告机(1)
- 词云中去重复的词_【需要拿去】毕业论文结束语、致谢词(通用版)
- SDN被放弃?5G承载网:我一点也不慌!