并发编程—Java线程状态及状态转换

线程是 JVM 执行任务的最小单元,理解线程的状态转换是理解Java多线程问题的基础。

Java线程有哪些状态?

在 JVM 运行中,线程一共有 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED 六种状态,这些状态对应 Thread.State 枚举类中的状态。

Thread.State源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,

/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,

/**
* 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,

/**
* 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,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

在指定的时间点,线程只能处于这些状态中的一种状态。这些状态都是Java语言中定义的,并不能反映操作系统的线程状态。

线程状态如何转换

Java线程中的各个状态转换可以参考下图,本文后面会做一些详细说明。

线程状态详细说明

初始(NEW)

创建一个新的Thread实例,但还未调用start方法,此时线程就处于初始状态。

可运行(RUNNABLE)

运行Threadstart方法后,线程进入RUNNABLE可运行状态。

RUNNABLE状态中还包含了两种可以互相转换的细分状态:READYRUNNING
线程处于RUNNABLE状态时,只能说明线程是可运行,即可以随时被操作系统调度让CPU去执行这个线程。当CPU正在执行线程时就处于RUNNING状态,当CPU的时间片用完但线程还没有结束,调度系统调用Thread.yield()方法,线程进入READY状态,等待系统的下一次调度。随后,操作系统将从优先级最高的那些等待线程中再挑选一个线程让CPU去执行,当前线程如果又获得了CPU的执行权,则会进入到RUNNING状态继续运行,否则继续处于READY状态。

终止(TERMINATED)

线程执行完了run()方法或者main()方法,或者因异常退出了这两个方法,线程进入到TERMINATED状态,该状态表示该线程的生命周期结束。

演示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyThread extends Thread {
@Override
public void run() {
System.out.printf("%s线程运行\n", Thread.currentThread().getName());
}
}

/**
* 分别观察创建线程后、start()后、和线程退出后的线程状态。
*/
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("创建线程后,线程的状态为:%s", myThread.getState());
myThread.start();
System.out.println("调用start()方法后线程的状态为:%s", myThread.getState());
// 休眠50毫秒,等待MyThread线程执行完
Thread.sleep(50);
System.out.println("休眠50毫秒后再次打印线程的状态为:%s", myThread.getState());

}
}

输出:

1
2
3
4
创建线程后,线程的状态为:NEW
调用start()方法后线程的状态为:RUNNABLE
Thread-0线程运行
休眠50毫秒后再次打印线程的状态为:TERMINATED

阻塞(BLOCKED)

线程在进入synchronized关键字修饰的方法或代码块时,如果获取锁失败(因为锁被其它线程所占用),它进入到BLOCKED状态。

演示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ThreadBlockedStateDemo {

public static void main(String[] args) {
Thread threadA = new Thread(() -> method01(), "A-Thread");
Thread threadB = new Thread(() -> method01(), "B-Thread");

threadA.start();
threadB.start();

System.out.println("线程A的状态为:%s", threadA.getState());
System.out.println("线程B的状态为:%s", threadB.getState());
}


public static synchronized void method01() {
System.out.println("[%s]:开始执行主线程的方法", Thread.currentThread().getName());
try {
//停顿10毫秒、模拟方法执行耗时
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[%s]:主线程的方法执行完毕", Thread.currentThread().getName());
}
}

等待(WAITING)

处于RUNNABLE的线程,有3中情况下会转换为WAITING状态:

  • 调用不带时限的Object.wait()方法
    当一个线程调用了一个对象的Object.wait()方法,线程将从RUNNABLE转换到WAITING状态,直到有另外一个线程调用了这个对象的Object.notify()Object.notifyAll()方法,那这个线程又将恢复到RUNNABLE状态。

  • 调用不带时限的Thread.join()方法
    例如有两个线程对象A和B,当B线程中调用 A.join() 的时候,B线程会等待A线程执行完,B线程状态将从RUNNABLE转换到WAITING。当线程A执行完,B线程又会从WAITING状态转换到RUNNABLE

  • 调用LockSupport.park()方法
    Java 并发包中的锁,都是基于LockSupport对象实现的。调用LockSupport.park()方法,当前线程会阻塞,线程的状态会从RUNNABLE转换到WAITING。调用LockSupport.unpark(Thread thread)方法,可唤醒目标线程,目标线程的状态又会从WAITING状态转换到RUNNABLE

超时等待(TIMED_WAITING)

TIMED_WAITING状态和WAITING状态的不同点在于多了一个超时的参数,即线程将在等待一定时间后自动恢复到RUNNABLE状态,不会无限期等待被其他线程显式地唤醒。

有5种场景会触发RUNNABLE状态向TIMED_WAITING转换:

  • 调用带超时参数的Thread.sleep(long millis)方法;
  • 调用带超时参数的Object.wait(long timeout)方法;
  • 调用带超时参数的Thread.join(long millis)方法;
  • 调用带超时参数的LockSupport.parkNanos(Object blocker, long deadline)方法;
  • 调用带超时参数的LockSupport.parkUntil(long deadline)方法。

状态转换的重要方法

wait() 、wait(long timeout) 和 wait(long timeout, int nanos)

wait()方法位于Object类中。在一个线程中调用某对象的wait()方法,会让当前线程进入WAIT状态,一般是跟notify方法配合使用,当其它线程调用了这个对象的notify()notifyAll()方法后将会唤醒该线程。注意,当前线程必须持有这个对象的锁才能调用wait()方法,否则会抛出IllegalMonitorStateException异常。调用对象的wait()方法后,将会释放这个对象的锁。代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void testWait() throws InterruptedException {
new Thread(() -> {
synchronized (obj) {
log.info("线程获取到锁");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程任务执行完成");
}
}, "wait线程").start();

TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
synchronized (obj) {
try {
log.info("线程获取到锁");
TimeUnit.SECONDS.sleep(5L);
obj.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "notify线程").start();

//睡眠6秒,保证两个线程都执行完
TimeUnit.SECONDS.sleep(6L);
}

输出:

1
2
3
17:32:33.741 [wait线程] INFO com.lzumetal.multithread.status.WaitTest - 线程获取到锁
17:32:34.757 [notify线程] INFO com.lzumetal.multithread.status.WaitTest - 线程获取到锁
17:32:39.763 [wait线程] INFO com.lzumetal.multithread.status.WaitTest - 线程任务执行完成

wait(long timeout)wait(long timeout, int nanos)这两个方法也是位于Object类中,和wait()方法区别就是带了毫秒级和纳秒级的超时参数。

notify() 和 notifyAll()

notify()同样是属于Object类的方法。。当前线程调用某个对象的notify()方法时也必须持有该对象锁,调用notify()方法后会唤醒另外一个等待这个锁的线程,让这个线程去争取同步锁。notifyAll()方法notify()差不多,不同的是它会唤醒其它所有等待这个锁的线程,这些线程被唤醒后都将会去争取同步锁。

Thread.sleep(long millis)

Thread.sleep(long millis)方法会暂停当前线程,把cpu片段让出给其他线程。如果当前线程持有锁,sleep方法不会释放锁。

Thread.yield()

yield()的作用是让步,让当前线程放弃获取的CPU时间片,由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

Thread.yield()不会导致阻塞。

yield() 与 wait()的比较
wait()的作用是等待,yield()的作用是让步,它们的区别:

  1. wait()是让线程由“运行状态”进入到“等待状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
  2. wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

thread.join()/thread.join(long millis)

当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,线程t执行完毕或者millis时间到,当前线程再重新往下执行。查看join(long millis)方法的源码是基于wait(long millis)方法实现,所以线程会释放当前的锁。

join(long millis)方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
*等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
*millis - 以毫秒为单位的等待时间。
*/
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) {//等待时间为0,则无限等待
//需要注意,如果当前线程未被启动或者终止,则isAlive方法返回false
//即意味着join方法不会生效
while (isAlive()) {
wait(0);
}
} else {
//需要注意,如果当前线程未被启动或者终止,则isAlive方法返回false
//即意味着join方法不会生效
while (isAlive()) {
//计算剩余的等待时间
long delay = millis - now;
if (delay <= 0) {//如果剩余的等待时间小于等于0,则终止等待
break;
}
//等待指定时间
wait(delay);
//获取当前时间
now = System.currentTimeMillis() - base;
}
}
}

------ 本文完 ------