一、线程的5种状态
![3c38902067d6f7f435ff2ed7f73b5c89.png](https://i-blog.csdnimg.cn/blog_migrate/c4822791bad43c0d64c50ac3c55cbc7a.jpeg)
众所周知,Java的线程状态有5种,分别对应上图中五种不同颜色,下面对这5种状态及状态间的转化做相应的解释:
1. 初始化状态:新建一个线程对象
2. 可运行状态:其他线程调用了该线程对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
3. 运行状态:可运行状态的线程获得了cpu 时间片(timeslice),执行程序代码
4. 阻塞状态:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程再次进入可运行状态,才有机会转到运行状态。如图所示,会有三种不同类型的阻塞状态:
等待阻塞
:运行中的线程执行wait()方法,线程会进入等待队列中。等待notify()、notifyAll()或interrupt()对其唤醒或中断同步阻塞
:运行中的线程执行在获取同步锁(注:只有synchronized这种方式的锁(monitor锁)才会让线程出现BLOCKED状态,等待ReentrantLock则不会)时,若该锁已被其他线程占用,线程则会进入锁池队列。等待获取到锁其他阻塞
:运行的线程执行sleep()、join(),或触发了I/O请求,该该线程被置为阻塞状态。当sleep()状态超时、join()等待线程终止或超时、I/O处理完成,线程会重新进入可运行状态。
5. 死亡状态:线程执行完或因异常退出run()方法,线程生命周期结束
看完上面的配图和简介,可能会有人对阻塞状态的几种情况有些疑问,下面我们就一点一点解开她的小裙子 图中同样都是Blocked状态,等待队列和锁池是什么鬼?
等待队列和锁池都和wait()、notify()、synchronized有关,wait()和notify()又必须由对象调用且必须写在synchronized同步代码块内。
- 等待队列(等待被唤醒):对应等待阻塞。调用obj的wait()方法,则进入等待队列
- 锁池(等待抢锁):对应同步阻塞。
a)当前running线程调用对象obj的同步方法时,发现锁被其他线程持有,则直接进入锁池。
b)当前等待队列中阻塞的线程A,等待被线程B唤醒,唤醒后并非直接进去runnable状态,而是进入线程A所对应的锁池中,等待抢到锁。
下图直观描绘了running->等待队列->锁池->runnable间的状态流转,帮助大家理解。
![a105d5fe08c83b298cca2f09c19ea79f.png](https://i-blog.csdnimg.cn/blog_migrate/025930413d8b933852823c524e8ef968.jpeg)
怎么样,有没有通透很多?啊!还是有些小迷糊。好,那我们下面从jdk源码中对线程状态的描述进行分析
二、Thread.State枚举类中的BLOCKED、WAITING、TIMED_WAITING
我们打开Jdk源码中Thread类,会发现里面有定义State的枚举,枚举中有:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。由于线程中的初始化和死亡状态很短,我们不用太关心,runnable状态暂无歧义。我们只针对BLOCKED、WAITING、TIMED_WAITING三种可能混淆的状态进行分析
首先,上源码
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
// 受阻塞并且正在等待monitor锁的某一线程的线程状态。
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
// 某一等待线程的线程状态。
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
// 具有指定等待时间的某一等待线程的线程状态
BLOCKED
场景:某一线程在等待monitor lock,比如在等待执行synchronized代码块/方法,这不就相当于进入了我们的锁池阻塞状态吗!;或在synchronized块/方法中循环调用obj的wait()方法,也就是线程在等待进入临界区。WAITING
场景:某一线程因为调用下列方法之一而处于等待状态:
- 不带超时值的 Object.wait
- 不带超时值的 Thread.join
- LockSupport.park 分析:既有可能进入等待队列,也有可能进入其他阻塞的阻塞状态
3. TIMED_WAITING
场景:某一线程因为调用以下带有指定正等待时间的方法之一而处于定时等待状态:
- Thread.sleep(long millis)
- 带有超时值的 Object.wait
- 带有超时值的 Thread.join
- LockSupport.parkNanos
- LockSupport.parkUntil 分析:既有可能进入等待队列,也有可能进入其他阻塞的阻塞状态。和WAITING区别在于是否指定时间
结合实际编程经验,及工作中所遇到的情形,仔细回味一下上面两个模块的内容,可能就会恍然大悟。
三、并发编程中常用的几个jdk中的api的理解,可能面试常问哦
- Thread.sleep(long millis) 静态方法。当前线程调用此方法,使当前线程进入阻塞状态(其他阻塞),但不释放任何锁资源,一定时间后线程自动进入runnable状态。给其它线程执行机会的最佳方式
- obj.wait()或obj.wait(long timeout) 当前线程调用某对象的wait()方法,当前线程释放对象锁(wait一定在synchronized代码块/方法中,故一定得到了锁,才进来的此方法),进入阻塞状态(等待队列)。等待notify或wait设置的timeout到期,方可进入另外一个阻塞状态(锁池)。
- t.join()或t.join(long millis) 非静态方法。当前线程A执行过程中,调用B线程的join方法,使当前线程进入阻塞状态(其他阻塞),但不释放对象锁,等待B线程执行完后或一定时间millis后,A线程进入runnable状态。
- Thread.yield() 静态方法。当前线程调用此方法,使线程由running态进入runnable态,放弃cpu使用权,让cpu再次选择要执行的线程。 注:实际过程中,yield仅仅是让其它具有同等优先级的runnable线程获取执行权,但并不能保证其它具有同等优先级的线程就一定能获得cpu执行权。因为做出让步的当前线程,可能会被cpu再次选中,进入running状态。yield()不会导致阻塞。
- 并发编程中为什么使用while循环而不是if()来调用wait方法 当其他获取到该对象锁的线程释放锁时,上面的线程有可能被意外唤醒,但是此时上面线程是不满足条件的,导致它破坏了被锁保护的约束关系,引起意外后果。 用while()方法,就会再次判断条件是不是成立,满足执行条件了,才会继续执行;而if会直接唤醒wait()方法,继续往下执行,不管被notify或notifyAll唤醒的是不是它,而极有可能,此时并不满足if的判断条件,就是JDK文档中所谓的“虚假唤醒”。
synchronized (Demo.class){
while(!执行条件) {
obj.wait();
}
....
if(!执行条件) {
obj.wait();
}
obj.wait();
}