当前位置:主页 > 查看内容

阅读 JDK 源码:线程类 Thread

发布时间:2021-05-14 00:00| 位朋友查看

简介:在 Java 中,使用 Thread 类可以在操作系统层面创建线程,并绑定到对应的 Thread 类实例中。利用线程异步地执行任务,是并发编程的基……

在 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 of Thread. This subclass should override the run method of class Thread. 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 the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, 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 自己是不会去干涉线程调度的(可以设置线程优先级给操作系统提供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的。
1:1线程模型

  • 轻量级进程(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)的线程处于这个状态。
分为两种情况:

  1. 首次进入 synchronized 块时等待获取锁;
  2. 在调用 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 状态转移

Thread State

4.4 状态分类

Java 线程 6 种状态看起来挺复杂的,但其实 BLOCKED,WATTING,TIMED_WAITING 都会使线程处于阻塞状态,所以我们将这三类都归类为阻塞状态。因此,Java 线程生命周期就可以简化为下图:

Thread State

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 执行完成。

分析一下整个过程:

  1. threadA 执行代码 threadB.join(),由于 Thread#join 方法是非静态的、由 synchronized 修饰的,threadA 首先需要获取 threadB 这个对象的锁。
  2. 若 threadA 获取不到锁,会进入阻塞状态 BLOCKED;若可以获取锁,则进入方法内部。
  3. 时间参数校验通过后,则会执行 Thread#isAlive 校验线程是否存活。注意,这里的 this.isAlive() 是 threadB 对象的方法,因此是 threadA 来检查 threadB 是否存活。
  4. 若 threadB 还存活,则 threadA 执行 threadB.wait() 会释放锁,进入无限期等待状态 WAITING。
  5. 当 threadB 结束运行,进入 TERMINATED 状态,会调用 threadB.notifyAll() 方法唤醒在 threadB.wait() 上等待的线程。
  6. 当 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 如何正确停止线程

  1. 任务中一般都会有循环结构,只要用一个标记控制住循环,就可以结束任务。
  2. 如果线程处于等待状态,无法读取标记,此时可以使用 interrupt() 方法将线程从等待状态强制恢复到运行状态中来,让线程具备 CPU 的执行资格,继而自行决定是否应该终止。

7. 总结

  1. 创建线程具有两种不同的含义,new Thread() 会在内存中创建 Thread 类实例,而 new Thread().start() 才会创建并启动操作系统原生线程。
  2. JVM 采用了 1:1 的线程模型,每一个 Java 线程都是直接映射到一个操作系统原生线程。
  3. Thread 类中定义了 6 种线程状态,不会映射到操作系统层面的线程状态。
  4. Thread#sleep、Object#wait、Thread#join、Thread#yield 和 LockSupport 相关方法都可以用于调度线程。
  5. 线程中具有中断状态,应当使用中断来终止一个线程的运行。当前线程被中断时,需要正确地处理中断。

作者:Sumkor
链接:https://segmentfault.com/a/11...


本文转自网络,版权归原作者所有,原文链接:https://segmentfault.com/a/1190000039998094
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐