04_线程状态转换方法
1、操作系统中的线程状态
操作系统中的线程状态有运行、就绪、等待三个关键状态
- 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态
- 执行状态(running):线程正在使用CPU
- 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)
Q
操作系统中的线程为什么没有挂起状态?
A
由于线程不是资源的拥有单位,挂起状态对线程是没有意义的,如果一个进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去。可见由挂起操作引起的状态是进程级状态,不作为线程级状态。类似地,进程的终止会导致进程中所有线程的终止。
2、Java中的六个线程状态
Java中的线程状态有六个线程状态。
Thread.State源码
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW 尚未启动的线程处于此状态。
RUNNABLE 在Java虚拟机中执行的线程处于这种状态。
BLOCKED 在等待监视器锁定的情况下被阻塞的线程处于此状态。
WAITING 无限期地等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING 正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。
TERMINATED 退出的线程处于此状态。
在给定的时间点,线程只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
Q
同一个线程调用两次start()方法是否可以?
A
在调用一次start()之后,threadStatus的值会改变(threadStatus !=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。
我们看一下start()方法的源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
//这个是native方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
猜想,new之后的threadStatus状态为0,但是调用native start0()方法后,状态会变为多少呢?我们一起debug一下。
package co.dianjiu.thread;
public class MyThreadStatus extends Thread{
@Override
public void run(){
System.out.println("MyThreadStatus");
}
public static void main(String[] args) {
MyThreadStatus myThreadStatus = new MyThreadStatus();
System.out.println(myThreadStatus.getState());
myThreadStatus.start();
System.out.println(myThreadStatus.getState());
myThreadStatus.start();
System.out.println(myThreadStatus.getState());
}
}
NEW
MyThreadStatus
RUNNABLE
Exception in thread “main” java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at co.dianjiu.thread.MyThreadStatus.main(MyThreadStatus.java:14)
看下getState()方法的源码
public State getState() {
// get current thread state
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
new 之后的 threadStatus断点进来的值为0
start 之后的 threadStatus断点进来的值为5
3、Java中的线程状态转换
3.1、阻塞状态和就绪状态的转换
处于BLOCKED状态的线程是因为在等待锁的释放。假如这里有两个线程a和b,a线程提前获得了锁并且暂未释放锁,此时b就处于BLOCKED状态。我们先来看一个例子:
package co.dianjiu.thread;
public class MyThreadBlocked extends Thread{
@Override
public void run(){
testMethod();
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
a.start();
// 需要注意这里main线程休眠了1000毫秒,而testMethod()里休眠了2000毫秒
Thread.sleep(1000L);
b.start();
System.out.println(a.getName() + ":" + a.getState());
System.out.println(b.getName() + ":" + b.getState());
}
// 同步方法争夺锁
private static synchronized void testMethod() {
try {
Thread.sleep(2000L);
System.out.println(Thread.currentThread().getName()+"====>"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果
a:TIMED_WAITING
b:BLOCKED
a====>RUNNABLE
b====>RUNNABLE
3.2、等待状态和就绪状态的转换
从上面的转换图可知,有三种方法可从等待状态转换到就绪状态,让我们一起看一下具体的使用案例及每个方法的源码实现。
Object.wait()
调用wait()方法前线程必须持有对象的锁。
线程调用wait()方法时,会释放当前的锁,直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。
package co.dianjiu.thread;
public class MyThreadWaiting extends Thread{
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
System.out.println("线程A等待获取lock锁");
synchronized (lock) {
try {
System.out.println("线程A获取了lock锁,将要运行lock.wait()方法进行等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a.setName("a");
Thread b = new Thread(() -> {
System.out.println("线程B等待获取lock锁");
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
synchronized (lock) {
System.out.println("线程B等待获取lock锁,将要运行lock.notify()方法");
lock.notify();
//lock.notifyAll();
System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
}
});
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
System.out.println(a.getName() + "===>" + a.getState());
b.start();
System.out.println(a.getName() + "===>" + a.getState());
}
}
执行结果
a===>NEW
b===>NEW
线程A等待获取lock锁
线程A获取了lock锁,将要运行lock.wait()方法进行等待
a===>RUNNABLE
线程B等待获取lock锁
a===>WAITING
b===>RUNNABLE
线程B等待获取lock锁,将要运行lock.notify()方法
a===>RUNNABLE
看下wait方法的源码,其实wait()方法就等于wait(Long)方法值为0
public final void wait() throws InterruptedException {
wait(0L);
}
而唤醒方法调用的是Java本地接口(JNI)
其他线程调用notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。同样,调用notifyAll()方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的那个线程,具体要看系统的调度。
@HotSpotIntrinsicCandidate
public final native void notify();
@HotSpotIntrinsicCandidate
public final native void notifyAll();
Thread.join()
调用join()方法不会释放锁,会一直等待当前线程执行完毕(转换为TERMINATED状态)。
package co.dianjiu.thread;
public class MyThreadJoin extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
a.join();
b.start();
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
}
}
执行结果
a===>NEW
b===>NEW
a====>RUNNABLE
a===>TERMINATED
b===>TIMED_WAITING
b====>RUNNABLE
看下join方法的源码,其实join()方法就等于join(Long)方法值为0
public final void join() throws InterruptedException {
join(0);
}
LockSupport.park()
在线程调度的时候禁用当前线程,将其放置到WaitSet队列。除非有许可证。所谓的许可证就是前面说的0-1的标识。使用unpark则就会产生一个许可。
如果许可证可用,那么它将被消耗,并且调用将立即返回;否则,出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一:
- 其他线程以当前线程为目标调用unpark方法。
- 其他线程打断当前线程。
- 调用了虚假的返回。
package co.dianjiu.thread;
import java.util.concurrent.locks.LockSupport;
public class MyThreadPark {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
System.out.println("线程A获取了lock锁,将要运行LockSupport.park()方法进行等待");
LockSupport.park(Thread.currentThread());
});
a.setName("a");
Thread b = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
System.out.println("线程B等待获取lock锁,将要运行LockSupport.unpark()方法");
LockSupport.unpark(a);
//a.interrupt();
System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
});
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
System.out.println(a.getName() + "===>" + a.getState());
b.start();
System.out.println(a.getName() + "===>" + a.getState());
}
}
a===>NEW
b===>NEW
线程A获取了lock锁,将要运行LockSupport.park()方法进行等待
a===>RUNNABLE
a===>WAITING
b===>RUNNABLE
线程B等待获取lock锁,将要运行LockSupport.unpark()方法
a===>RUNNABLE
3.3、超时等待和就绪状态的转换
Thread.sleep(long)
使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入RUNNABLE状态。
Object.wait(long)
wait(long)方法使线程进入TIMED_WAITING状态。这里的wait(long)方法与无参方法wait()相同的地方是,都可以通过其他线程调用notify()或notifyAll()方法来唤醒。
不同的地方是,有参方法wait(long)就算其他线程不来唤醒它,经过指定时间long之后它会自动唤醒,拥有去争夺锁的资格。
Thread.join(long)
join(long)使当前线程执行指定时间,并且使线程进入TIMED_WAITING状态。
我们再来改一改刚才的示例:
package co.dianjiu.thread;
public class MyThreadJoinTime extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
a.join(1000L);
b.start();
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
}
}
执行结果
a===>NEW
b===>NEW
a===>TIMED_WAITING
b===>BLOCKED
a====>RUNNABLE
b====>RUNNABLE
因为是指定了具体a线程执行的时间的,并且执行时间是小于a线程sleep的时间,所以a线程状态输出TIMED_WAITING。b线程状态仍然不固定(RUNNABLE或BLOCKED)。
3.4、线程中断状态
在某些情况下,我们在线程启动后发现并不需要它继续执行下去时,需要中断线程。目前在Java里还没有安全直接的方法来停止线程,但是Java提供了线程中断机制来处理需要中断线程的情况。
线程中断机制是一种协作机制。需要注意,通过中断操作并不能直接终止一个线程,而是通知需要被中断的线程自行处理。
简单介绍下Thread类里提供的关于线程中断的几个方法:
- Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为true(默认是flase);
- Thread.interrupted():测试当前线程是否被中断。线程的中断状态受这个方法的影响,意思是调用一次使线程中断状态设置为true,连续调用两次会使得这个线程的中断状态重新转为false;
- Thread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。
在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程自己而定,可以在合适的实际处理中断请求,也可以完全不处理继续执行下去。