在 Java 中,使用 Thread 类可以在操作系统层面创建线程,并绑定到对应的 Thread 类实例中。利用线程异步地执行任务,是并发编程的基础。本文通过阅读 Thread 源码,了解线程状态的定义,线程调度的相关方法,以及对线程中断的处理等。
本文基于 jdk1.8.0_91
1. 继承体系
线程类 Thread 实现了 Runnable 接口,并且具有一个 Runnable 属性,表示需要线程执行的任务。
Thread#run 方法内部调用了属性 Runnable 的 run 方法;若 Runnable 属性为空则什么也不做。
/**
* A <i>thread</i> is a thread of execution in a program. The Java
* Virtual Machine allows an application to have multiple threads of
* execution running concurrently.
*
* @author unascribed
* @see Runnable
* @see Runtime#exit(int)
* @see #run()
* @see #stop()
* @since JDK1.0
*/
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
如果直接执行 Thread#run 只是一个普通的调用,并不会启动新的线程来执行任务。
正确是用法是执行 Thread#start 来启动新的线程,由该线程来调用 Thread#run 执行任务。
2. 线程的创建
“创建线程”是一个模糊的概念,某些场景下指的是在内存中创建 Thread 类的实例,在另一些场景下指的是在操作系统层面创建原生线程。为了区分这两种截然不同的情况,本文将创建 Thread 类的实例称为 “创建线程”,将创建操作系统原生线程称为 “启动线程”。
2.1 构造函数
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}
最终都是调用 Thread#init 方法:
/**
* Initializes a Thread with the current AccessControlContext.
* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
...
}
入参说明:
- ThreadGroup g(线程组)
- Runnable target (Runnable 对象,表示线程需执行的任务)
- String name (线程的名字)
- long stackSize (为线程分配的栈的大小,若为 0 则表示忽略这个参数)
- AccessControlContext acc (继承给线程的访问权限控制上下文,默认为空,表示使用 AccessController.getContext())
2.2 创建线程实例
在 JDK 官方文档中,介绍了 Thread 类实例化的两种方式:
There are two ways to create a new thread of execution.
One is to declare a class to be a subclass ofThread
. This subclass should override therun
method of classThread
. An instance of the subclass can then be allocated and started.
The other way to create a thread is to declare a class that implements theRunnable
interface. That class then implements therun
method. An instance of the class can then be allocated, passed as an argument when creatingThread
, and started.
方式一:继承 Thread 类
编写 Thread 的子类,重写 Thread 类的 run 方法。创建该子类的实例以启动线程执行任务。
例如,计算大于某一规定值的质数的线程可以写成:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
...
}
}
下列代码会创建并启动一个线程:
PrimeThread p = new PrimeThread(143);
p.start();
方式二:实现 Runnable 接口
编写 Runnable 接口实现类,实现 Runnable 接口的 run 方法。在创建 Thread 时将该 Runnable 类的实例作为一个参数来传递。
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
...
}
}
下列代码会创建并启动一个线程:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
3. 线程的启动
3.1 启动系统线程
执行 new java.lang.Thread().start()
会在操作系统上创建并启动一个原生线程。
- java.lang.Thread#start 方法,内部调用了 native 的 start0 方法。该方法会由 JVM 映射到系统层面创建并启动线程,将该线程与 java.lang.Thread 对象进行绑定,随后 JVM 会调用该 java.lang.Thread 对象的 run 方法。
- 该操作的结果是会出现两个并发执行的线程,一个是发起 Thread#start 调用的当前线程,另一个是新创建的会执行 Thread#run 的线程。
- 注意,多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java.lang.Thread#start
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0(); // 由 JVM 映射到系统层面,创建线程!
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
3.2 JVM 线程模型
根据《深入理解 Java 虚拟机》第十二章第四节,实现线程主要有三种方式:
- 使用内核线程实现(1:1 实现);
- 使用用户线程实现(1:N 实现);
- 使用用户线程加轻量级进程混合实现(N:M 实现)。
HotSpot 虚拟机采用 1:1 的线程模型,它的每一个 Java 线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以 HotSpot 自己是不会去干涉线程调度的(可以设置线程优先级给操作系统提供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的。
- 轻量级进程(Light Weight Process,LWP)是内核线程的一种高级接口,就是我们通常意义上所讲的线程。程序一般不会直接使用内核线程,而是使用轻量级进程。
- 内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel)支持的线程,这种线程由内核来完成线程切换。
- 内核(Kernel)通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。
4. 线程的状态
4.1 状态定义
在 java.lang.Thread 类中,定义了 threadStatus 属性和 State 枚举来描述线程的状态。
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
/**
* A thread state.
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
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;
}
4.2 状态说明
根据定义,线程具有 6 种状态:
- NEW:A thread that has not yet started is in this state.
- RUNNABLE:A thread executing in the Java virtual machine is in this state.
- BLOCKED:A thread that is blocked waiting for a monitor lock is in this state.
- WAITING:A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
- TIMED_WAITING:A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
- TERMINATED:A thread that has exited is in this state.
线程在同一时间下,只会处于一种状态。这些状态是在 JVM 层面的,不会映射到操作系统层面的线程状态。
NEW
尚未启动的线程处于该状态。
RUNNABLE
在 JVM 中运行的线程处于该状态。
该状态下的线程正在 JVM 中运行,但是可能需要等待操作系统的资源,如 CPU 调度等。
BLOCKED
等待监视器锁(monitor lock)的线程处于这个状态。
分为两种情况:
- 首次进入 synchronized 块时等待获取锁;
- 在调用 Object#wait、Object.wait 方法之后,重入 synchronized 块时等待获取锁。
WAITING
无限期等待中的线程处于该状态。
调用以下方法之一,线程会处于该状态。
- Object.wait
- Thread.join
- LockSupport.park
处于等待状态的线程,在等待的是其他线程执行特定的操作。
- 调用 Object.wait 的线程,等待其他线程调用 Object.notify 或 Object.notifyAll
- 调用 Thread.join 的线程,等待其他线程终止
- 调用 LockSupport.park 的线程,等待其他线程调用 LockSupport.unpark
TIMED_WAITING
有限期等待中的线程处于该状态。
调用以下方法之一(入参均需要传入超时时间),线程会处于该状态。
- Thread.sleep
- Object.wait(long)
- Thread.join(long)
- LockSupport.parkNanos
- LockSupport.parkUntil
TERMINATED
已退出的线程处于该状态。
说明线程已经结束运行。
4.3 状态转移
4.4 状态分类
Java 线程 6 种状态看起来挺复杂的,但其实 BLOCKED,WATTING,TIMED_WAITING 都会使线程处于阻塞状态,所以我们将这三类都归类为阻塞状态。因此,Java 线程生命周期就可以简化为下图:
4.5 状态方法
在 java.lang.Thread 类中,定义了 getState() 方法获取线程的状态,定义了 isAlive() 方法判断当前线程是否活跃。
/**
* Returns the state of this thread.
* This method is designed for use in monitoring of the system state,
* not for synchronization control.
*
* @return this thread's state.
* @since 1.5
*/
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
*
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/
public final native boolean isAlive();
利用 isAlive() 方法,可以用于判断当前 Thread 类实例是否绑定了系统原生线程。
@Test
public void state() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("thread.isAlive() = " + thread.isAlive()); // false
System.out.println("thread.getState() = " + thread.getState());// NEW
thread.start();
Thread.sleep(200);
System.out.println("thread.isAlive() = " + thread.isAlive()); // true
System.out.println("thread.getState() = " + thread.getState());// TIMED_WAITING
thread.join();
System.out.println("thread.isAlive() = " + thread.isAlive()); // false
System.out.println("thread.getState() = " + thread.getState());// TERMINATED
}
5. 线程调度
当前线程调用 Thread#sleep、Object#wait、Thread#join 都会进入等待状态(WAITING/TIMED_WAITING)。
5.1 Thread#sleep
- Thread#sleep 是一个静态的 native 方法。
- 作用是使当前线程休眠一段时间,时间单位为毫秒。并且休眠过程中不会释放任何监视器锁。
- 调用了 Thread#sleep 之后,当前线程会进入 TIMED_WAITING 状态。
- 如果其他线程中断了当前线程,则当前线程从休眠中被唤醒,会清除中断状态,并抛出 InterruptedException。
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
Thread#sleep 还有另外一个版本,允许同时传入毫秒值和纳秒值,但是多出来的 nanos 最多只会让线程多休眠 1 毫秒,不常用。Thread#join 和 Object#wait 中都具有类似的需要传入毫秒值和纳秒值的方法,后续不再赘述。
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds plus the specified
* number of nanoseconds, subject to the precision and accuracy of system
* timers and schedulers. The thread does not lose ownership of any
* monitors.
*
* @param millis
* the length of time to sleep in milliseconds // 毫秒
*
* @param nanos
* {@code 0-999999} additional nanoseconds to sleep // 纳秒
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative, or the value of
* {@code nanos} is not in the range {@code 0-999999}
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
关于 Thread#sleep(0)
- Thread.Sleep(0) 并非是真的要线程挂起 0 毫秒,意义在于这次调用 Thread.Sleep(0) 的当前线程确实的被冻结了一下,让其他线程有机会优先执行。
- Thread.Sleep(0) 是当前线程暂时放弃 CPU,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。
5.2 Object#wait
- Object#wait 是一个非静态的 native 方法,需要在对象实例上使用。
- 作用是使当前线程等待其他线程执行特定的操作,时间单位为毫秒。
- 调用 Object#wait 方法之前,线程必须持有监视器锁(monitor lock)。
- 线程在等待过程中,会释放锁,进入 TIMED_WAITING 或 WAITING 状态。
- 线程从等待中被唤醒,重新等待获取锁,进入 BLOCKED 状态。
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or
* some other thread interrupts the current thread, or a certain
* amount of real time has elapsed.
*/
public final native void wait(long timeout) throws InterruptedException;
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
*/
public final void wait() throws InterruptedException {
wait(0);
}
用法示例:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
... // Perform action appropriate to condition
}
关于 Object#wait(0)
- Object#wait() 内部实际上是调用 Object#wait(timeout),超时时间为 0 毫秒,指的是没有超时时间。
- Object#wait(0) 被唤醒的条件:其他线程调用了 Object#notify 或 Object#notifyAll,或者当前线程发生了中断,或者发生虚假唤醒(spurious wakeup)。
- Object#wait(timeout) 被唤醒的条件:其他线程调用了 Object#notify 或 Object#notifyAll,或者当前线程发生了中断,或者发生虚假唤醒,或者等待超时。
5.3 Thread#join
- Thread#join 是一个非静态且非 native 方法,需要在线程实例上使用。
- 作用是使当前线程等待指定线程执行完毕,时间单位为毫秒。
/**
* Waits for this thread to die.
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
调用 Thread#join 涉及到两个线程之间的交互。
比如线程 threadA 在运行过程中,碰到 threadB.join()
这一行代码,表示 threadA 需要进入等待,直到 threadB 执行完成。
分析一下整个过程:
- threadA 执行代码
threadB.join()
,由于 Thread#join 方法是非静态的、由 synchronized 修饰的,threadA 首先需要获取 threadB 这个对象的锁。 - 若 threadA 获取不到锁,会进入阻塞状态 BLOCKED;若可以获取锁,则进入方法内部。
- 时间参数校验通过后,则会执行 Thread#isAlive 校验线程是否存活。注意,这里的
this.isAlive()
是 threadB 对象的方法,因此是 threadA 来检查 threadB 是否存活。 - 若 threadB 还存活,则 threadA 执行
threadB.wait()
会释放锁,进入无限期等待状态 WAITING。 - 当 threadB 结束运行,进入 TERMINATED 状态,会调用
threadB.notifyAll()
方法唤醒在threadB.wait()
上等待的线程。 - 当 threadA 从等待状态 WAITING 被唤醒后,重新获取 threadB 对象锁,循环检查 threadB 线程是否已存活,若不存活则结束等待。
JDK 源码注释中说明了,当线程终止时,会调用 Object#notifyAll 方法。
As a thread terminates the {@code this.notifyAll} method is invoked.
示例代码:
@Test
public void join() throws InterruptedException {
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 开始运行...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 结束运行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadB");
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 开始运行...");
threadB.start();
threadB.join();
System.out.println(Thread.currentThread().getName() + " 结束运行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA");
threadA.start();
threadA.join();
}
执行结果:
threadA 开始运行...
threadB 开始运行...
threadB 结束运行...
threadA 结束运行...
关于 Thread#join(0)
Thread#join() 内部实际上是调用 Thread#join(timeout),超时时间为 0 毫秒,指的是没有超时时间。
5.4 Thread#yield
线程调用 Thread#yield 并不会进入等待状态。
- Thread#yield 是一个静态的 native 方法。
- 作用是使当前线程让出 CPU。但对于 CPU 只是一个建议,有可能一个线程刚让出 CPU,然后又立马获得了 CPU。
- 与之相对,Thread#sleep 方法一定会让出 CPU 资源,并且休眠指定的时间,不参与 CPU 的竞争。
- 执行 Thread#yield 后线程仍处于 RUNNABLE 状态,而执行 Thread#sleep 后线程会从 RUNNABLE 变为 TIMED_WAITING 状态。
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
6. 中断
中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。但是,没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
此外,虽然 Thread.stop 确实停止了一个正在运行的线程,但是这种方式是不安全的,可能会产生不可预料的结果,在 JDK 中已标记为过时方法。
6.1 中断状态
在 Java 中,每一个线程都有一个中断标志位,该标志位用于表示线程的中断状态:已中断、未中断。
在 java.lang.Thread 中提供了检查、清除和设置中断标识的方法。
6.2 检查中断
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
java.lang.Thread#isInterrupted(boolean) 是一个 native 方法,用于检查线程是否已中断,入参 ClearInterrupted 用于控制是否清除中断状态。
调用该 native 方法的具有以下两个:
Thread#isInterrupted 检查指定线程的中断状态,不清除该线程的中断状态。
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
Thread#interrupted 检查当前线程的中断状态,并清除当前线程的中断状态。
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
6.2 发起中断
- 设置线程的中断状态为已中断。
- 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者调用 Thread 类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
- 中断一个已经终止的线程不会有任何影响。
也就是说,threadA 执行 threadB.interrupt()
,则 threadB 会出现以下两种结果之一(取决于 threadB 自身):
- threadB 的中断状态为未中断,且抛出 InterruptedException;
- threadB 的中断状态为已中断,不抛出异常。
java.lang.Thread#interrupt
/**
* Interrupts this thread.
*
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @revised 6.0
* @spec JSR-51
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
private native void interrupt0();
6.3 如何正确处理中断
已知线程调用 Object#wait、Thread#join、Thread#sleep 方法进入等待状态时,会被其他线程调用 Thread#interrupt 唤醒。
此时当前线程的中断状态会被清除,并抛出 InterruptedException。
如果当前线程处于某种原因无法传递 InterruptedException 异常,最好通过再次调用 interrupt 来恢复中断的状态,以供上层调用者处理。
错误的处理方式:
void func() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// nothing
}
}
正确的处理方式:
void func() throw InterruptedException {
Thread.sleep(50);
}
或者
void func() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
6.4 不要在循环检查中断
如果在循环中调用 sleep,除非正确地处理中断异常,否则不要去检测中断状态。
错误的处理方式:
@Test
public void dealInterrupt() {
Thread subThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted()) { // 循环检查中断状态
try {
System.out.println(Thread.currentThread().getName() + " 开始第【" + i + "】休眠...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 结束第【" + i + "】休眠...");
++i;
} catch (InterruptedException e) { // 如果调用sleep受阻,会抛出异常,同时中断状态true将被清除为false
System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
// 只要正确地处理中断,也可以让循环停止。
// Thread.currentThread().interrupt();
}
}
}
});
subThread.start();
// 主线程执行一段时间,中断子线程,再继续观察子线程一段时间
try {
Thread.sleep(1000);
subThread.interrupt();
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
}
}
该例子会出现两种执行结果:
如果子线程在休眠当中被中断,由于会清除中断状态,导致子线程会无限循环下去。
Thread-0 开始第【0】休眠...
Thread-0 sleep interrupted
Thread-0 开始第【0】休眠...
Thread-0 结束第【0】休眠...
Thread-0 开始第【1】休眠...
Thread-0 结束第【1】休眠...
Thread-0 开始第【2】休眠...
Thread-0 结束第【2】休眠...
Thread-0 开始第【3】休眠...
Thread-0 结束第【3】休眠...
Thread-0 开始第【4】休眠...
Thread-0 结束第【4】休眠...
Thread-0 开始第【5】休眠...
如果子线程在休眠过后被中断,由于会设置中断状态,子线程可以得到终止。
Thread-0 开始第【0】休眠...
Thread-0 结束第【0】休眠...
正确的处理方式:
@Test
public void dealInterrupt02() {
Thread subThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
boolean isLoop = true;
while (isLoop) { // 使用自定的循环控制标识
try {
System.out.println(Thread.currentThread().getName() + " 开始第【" + i + "】休眠...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 结束第【" + i + "】休眠...");
++i;
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
isLoop = false;
}
}
}
});
subThread.start();
// 主线程执行一段时间,中断子线程,再继续观察子线程一段时间
try {
Thread.sleep(1000);
subThread.interrupt();
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
}
}
6.5 如何正确停止线程
- 任务中一般都会有循环结构,只要用一个标记控制住循环,就可以结束任务。
- 如果线程处于等待状态,无法读取标记,此时可以使用 interrupt() 方法将线程从等待状态强制恢复到运行状态中来,让线程具备 CPU 的执行资格,继而自行决定是否应该终止。
7. 总结
- 创建线程具有两种不同的含义,
new Thread()
会在内存中创建 Thread 类实例,而new Thread().start()
才会创建并启动操作系统原生线程。 - JVM 采用了 1:1 的线程模型,每一个 Java 线程都是直接映射到一个操作系统原生线程。
- Thread 类中定义了 6 种线程状态,不会映射到操作系统层面的线程状态。
- Thread#sleep、Object#wait、Thread#join、Thread#yield 和 LockSupport 相关方法都可以用于调度线程。
- 线程中具有中断状态,应当使用中断来终止一个线程的运行。当前线程被中断时,需要正确地处理中断。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。