众所周知,在Java中Thread 有六种状态,分别是 “新建状态”、“可运行状态”、“终止状态”、“阻塞状态”、“等待状态”、“限时等待状态”。本次讨论我们只关注"等待状态"、“限时等待状态”、“阻塞状态”的区别。本人知识储备比较浅,故本文只是对我已有的相关知识的一个小总结。
一、导致线程处于“等待状态”、“限时等待状态”的原因
(一)等待状态
调用以下的方法会导致线程进入“等待状态”:
1、不带超时值的Object.wait
2、不带超时值的Thread.join
3、LockSupport.park
这些方法都有一个共同的特点,就是Thread需要等待某个条件被满足时,才会转变为“可运行状态”。
(二)限时等待状态
调用以下的方法会导致线程进入“限时等待状态”:
1、Thread.sleep
2、带有超时值的 Object.wait
3、带有超时值的 Thread.join
4、LockSupport.parkNanos
5、LockSupport.parkUntil
这些方法都有一个共同的特点,就是Thread需要 等待某个条件被满足 或 等待超时 时,才会转变为“可运行状态”。
二、导致线程处于“阻塞状态”的原因
等待同步代码块/方法时,未能获取到锁(即没有成为与锁对象关联的Monitor对象的所有者)。等另一个线程释放锁,并且当前线程获取到锁时,才会转变为“可运行状态”。
注:从上面的描述来看,处于“阻塞状态”的线程要想转变为“可运行状态”,好像也有一个等待状态(即获取到锁)。那么“等待状态”等待的条件 与 “阻塞状态”等待的条件有什么区别呢?首先前者等待的条件种类比较多,后者等待的条件只有一个(获取到锁)。故两者实现的机制也不同。
三、Monitor(锁)
每个临界区都有一个用于获取锁的【锁对象】,如以下同步代码块中的【obj对象】。
首先会将synchronized中的锁对象的对象头的MarkWord字段去尝试指向操作系统提供的Monitor对象,让锁对象中的MarkWord字段和Monitor对象相关联. 如果关联成功, 将obj对象头中的MarkWord字段值从01改为10。
【锁对象的对象头的MarkWord字段】 指向 【Monitor对象】,且该【Monitor对象】是被独享的,即【锁对象】与【Monitor对象】之间是一一对应的。【Monitor对象】其实就是严格意义上的锁,而【锁对象】更像是锁的一个引用。【Monitor对象】的 【Owner字段 】记录着【持有锁的线程】,即锁的所有者。而在抢占锁的过程中,没有获得锁的线程会被保存在【 EntryList】 中,等待【持有锁的线程】释放锁时重新抢占锁。
以Object.wait为例。调用【锁对象】的【wait()方法】,会导致线程会释放持有的锁,同时线程对象被添加到【WaitSet】中,而被添加【WaitSet】中的该线程会 等待被其他线程唤醒 或 等待超 时。在【WaitSet】中的线程 被唤醒 或 超时 后,会被转移到【EntryList】中,等待抢占锁。
拓展的内容:如何中断线程的阻塞?
从广义上的“阻塞”来说,Java中有三种:
1、线程处于"等待状态"、“限时等待状态”的阻塞。
2、线程处于 "阻塞状态"的阻塞。
3、I/O操作导致的阻塞(此时线程处于“可运行状态”)。
对Java中的线程中断机制而言,只有第一种阻塞会响应中断请求。对于第二种阻塞而言,可以通过使用实现了Lock接口的锁来获得可中断的特性。对于第三种阻塞而言,可以通过关闭I/O流之类的方法引起I/O异常,进而达到中断I/O操作实现中断阻塞的目的。