线程是 JVM 执行任务的最小单元,理解线程的状态转换是理解Java多线程问题的基础。
Java线程有哪些状态?
在 JVM 运行中,线程一共有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,这些状态对应 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
64public 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)
运行Thread
的start
方法后,线程进入RUNNABLE
可运行状态。
RUNNABLE
状态中还包含了两种可以互相转换的细分状态:READY
和RUNNING
。
线程处于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
22class MyThread extends Thread {
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
26public 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
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
317: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()
的作用是让步,它们的区别:
wait()
是让线程由“运行状态”进入到“等待状态”,而不yield()
是让线程由“运行状态”进入到“就绪状态”。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;
}
}
}