【Java 多线程并发】 Thread

Metadata

title: 【Java 多线程并发】 Thread
date: 2023-07-03 09:54
tags:
  - 行动阶段/完成
  - 主题场景/程序
  - 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
  - 细化主题/Java
categories:
  - Java
keywords:
  - Java
description: Thread类是Java多线程编程的核心类,通过它可以创建和管理线程,实现多任务并发执行。合理使用Thread类可以提高程序的性能和效率,但也需要注意线程安全问题。

概述

Thread类是Java中用于创建和管理线程的类。

Thread类的继承关系

Thread 数据结构

//当前线程的名称
private volatile String name;
//线程的优先级
private int            priority;
private Thread threadQ;
private long eetop;
//当前线程是否是单步线程
private boolean single_step;
//当前线程是否在后台运行
private boolean     daemon = false;
//Java虚拟机的状态
private boolean     stillborn = false;
//真正在线程中执行的任务
private Runnable target;
//当前线程所在的线程组
private ThreadGroup group;
//当前线程的类加载器
private ClassLoader contextClassLoader;
//访问控制上下文
private AccessControlContext inheritedAccessControlContext;
//为匿名线程生成名称的编号
private static int threadInitNumber;
//与此线程相关的ThreadLocal,这个Map维护的是ThreadLocal类
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程相关的ThreadLocal
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理
private long stackSize;
//线程终止后存在的JVM私有状态
private long nativeParkEventPointer;
//线程的id
private long tid;
//用于生成线程id
private static long threadSeqNumber;
//当前线程的状态,初始化为0,代表当前线程还未启动
private volatile int threadStatus = 0;
//由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置
//使用java.util.concurrent.locks.LockSupport.getBlocker访问
volatile Object parkBlocker;
//Interruptible接口中定义了interrupt方法,用来中断指定的线程
private volatile Interruptible blocker;
//当前线程的内部锁
private final Object blockerLock = new Object();
//线程拥有的最小优先级
public final static int MIN_PRIORITY = 1;
//线程拥有的默认优先级
public final static int NORM_PRIORITY = 5;
//线程拥有的最大优先级
public final static int MAX_PRIORITY = 10;

线程的状态定义

public enum State {
 //初始化状态
    NEW,
 //可运行状态,此时的可运行包括运行中的状态和就绪状态
    RUNNABLE,
 //线程阻塞状态
    BLOCKED,
 //等待状态
    WAITING,
 //超时等待状态
    TIMED_WAITING,
 //线程终止状态
    TERMINATED;
}

init()方法

  1. 设置线程名
  2. 父线程设置
  3. 安全校验
  4. 设置操作

run()方法

调用了Runnable对象的run()方法

start()方法

start()方法使用synchronized关键字修饰,说明start()方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。
一个线程只能启动一次,多次启动是会抛出异常的。

sleep()方法

使当前线程休眠

join()方法

一直等待线程超时或者终止

  1. 首先,代码检查传入的等待时间是否为负数,如果是负数则抛出IllegalArgumentException异常。
  2. 然后,代码根据传入的等待时间进行不同的处理
  3. 如果等待时间不为0,代码进入另一个循环,该循环会在给定的时间内等待当前线程终止。
  4. 最后,当当前线程终止时,代码退出循环,方法执行结束。

interrupt()方法

中断当前线程的方法

这段代码实现了中断线程的功能。当线程被中断时,如果线程正在阻塞状态,则会通过 blocker 对象来中断线程;如果线程没有被阻塞,则直接设置中断标志。

  1. 代码检查当前线程是否是自身
  2. 代码使用 synchronized 关键字来同步访问 blockerLock 对象
  3. 接下来,代码获取 blocker 对象的引用。
  4. 如果 blocker 对象不为空,说明线程正在被阻塞。此时,代码调用 interrupt0() 方法来设置中断标志,并调用 blocker.interrupt(this) 方法来通知 blocker 对象中断当前线程。然后,方法返回。
  5. 如果 blocker 对象为空,说明线程没有被阻塞。此时,代码直接调用 interrupt0() 方法来设置中断标志。

【Java 多线程并发】 Thread

Thread类是Java中用于创建和管理线程的类。它提供了一系列方法来操作线程,包括线程的启动、暂停、恢复、中断等。通过使用Thread类,可以实现多线程的并发执行,提高程序的性能和效率。

Thread类可以通过两种方式来创建线程:继承Thread类并重写run()方法,或者实现Runnable接口。通过重写run()方法,可以定义线程的执行逻辑。线程的启动通过调用start()方法来实现,该方法会启动一个新的线程,并自动调用run()方法。

Thread类还提供了其他一些方法来管理和控制线程的行为。例如,可以使用sleep()方法让线程暂停执行一段时间,使用join()方法等待线程执行完成,使用interrupt()方法中断线程的执行,使用isAlive()方法判断线程是否处于活动状态等。

总结来说,Thread类是Java多线程编程的核心类,通过它可以创建和管理线程,实现多任务并发执行。合理使用Thread类可以提高程序的性能和效率,但也需要注意线程安全问题。

Thread类的继承关系

由上图我们可以看出,Thread类实现了Runnable接口,而Runnable在JDK 1.8中被@FunctionalInterface注解标记为函数式接口,Runnable接口在JDK 1.8中的源代码如下所示。

undefined

Thread类的源码剖析

Thread类定义

Thread在java.lang包下,Thread类的定义如下所示。

public class Thread implements Runnable {
    ...
}

加载本地资源

打开Thread类后,首先,我们会看到在Thread类的最开始部分,定义了一个静态本地方法registerNatives(),这个方法主要用来注册一些本地系统的资源。并在静态代码块中调用这个本地方法,如下所示。

//定义registerNatives()本地方法注册系统资源
private static native void registerNatives();
static {
 //在静态代码块中调用注册本地系统资源的方法
 registerNatives();
}

Thread中的成员变量

Thread类中的成员变量如下所示。

//当前线程的名称
private volatile String name;
//线程的优先级
private int            priority;
private Thread threadQ;
private long eetop;
//当前线程是否是单步线程
private boolean single_step;
//当前线程是否在后台运行
private boolean     daemon = false;
//Java虚拟机的状态
private boolean     stillborn = false;
//真正在线程中执行的任务
private Runnable target;
//当前线程所在的线程组
private ThreadGroup group;
//当前线程的类加载器
private ClassLoader contextClassLoader;
//访问控制上下文
private AccessControlContext inheritedAccessControlContext;
//为匿名线程生成名称的编号
private static int threadInitNumber;
//与此线程相关的ThreadLocal,这个Map维护的是ThreadLocal类
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程相关的ThreadLocal
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理
private long stackSize;
//线程终止后存在的JVM私有状态
private long nativeParkEventPointer;
//线程的id
private long tid;
//用于生成线程id
private static long threadSeqNumber;
//当前线程的状态,初始化为0,代表当前线程还未启动
private volatile int threadStatus = 0;
//由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置
//使用java.util.concurrent.locks.LockSupport.getBlocker访问
volatile Object parkBlocker;
//Interruptible接口中定义了interrupt方法,用来中断指定的线程
private volatile Interruptible blocker;
//当前线程的内部锁
private final Object blockerLock = new Object();
//线程拥有的最小优先级
public final static int MIN_PRIORITY = 1;
//线程拥有的默认优先级
public final static int NORM_PRIORITY = 5;
//线程拥有的最大优先级
public final static int MAX_PRIORITY = 10;

从Thread类的成员变量,我们可以看出,Thread类本质上不是一个任务,它是一个实实在在的线程对象,在Thread类中拥有一个Runnable类型的成员变量target,而这个target成员变量就是需要在Thread线程对象中执行的任务。

线程的状态定义

在Thread类的内部,定义了一个枚举State,如下所示。

public enum State {
 //初始化状态
    NEW,
 //可运行状态,此时的可运行包括运行中的状态和就绪状态
    RUNNABLE,
 //线程阻塞状态
    BLOCKED,
 //等待状态
    WAITING,
 //超时等待状态
    TIMED_WAITING,
 //线程终止状态
    TERMINATED;
}

这个枚举类中的状态就代表了线程生命周期的各状态。我们可以使用下图来表示线程各个状态之间的转化关系。

  • NEW:初始状态,线程被构建,但是还没有调用start()方法。
  • RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。
  • BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。
  • WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。
  • TIME_WAITING:超时等待状态。可以在一定的时间自行返回。
  • TERMINATED:终止状态,当前线程执行完毕。

Thread类的构造方法

精简版

public Thread() {
 init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
 init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
 init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
 init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
 init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
 init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
 init(null, target, 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);
}

中文注释版

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (null, null, gname)}具有相同的效果,其中{@code gname}是一个新生成的名称。自动生成的名称的格式为{@code "Thread-"+}<i>n</i>,其中<i>n</i>是一个整数。
 */
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (null, target, gname)}具有相同的效果,其中{@code gname}是一个新生成的名称。自动生成的名称的格式为{@code "Thread-"+}<i>n</i>,其中<i>n</i>是一个整数。
 *
 * @param  target
 *         当线程启动时,调用此线程的对象的{@code run}方法。如果为{@code null},则此类的{@code run}方法不执行任何操作。
 */
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

/**
 * 创建一个继承给定AccessControlContext的新线程。这不是一个公共构造函数。
 */
Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (group, target, gname)}具有相同的效果,其中{@code gname}是一个新生成的名称。自动生成的名称的格式为{@code "Thread-"+}<i>n</i>,其中<i>n</i>是一个整数。
 *
 * @param  group
 *         线程组。如果为{@code null}并且存在安全管理器,则线程组由{@linkplain SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}确定。如果没有安全管理器或{@code SecurityManager.getThreadGroup()}返回{@code null},则线程组设置为当前线程的线程组。
 *
 * @param  target
 *         当线程启动时,调用此线程的对象的{@code run}方法。如果为{@code null},则调用此线程的run方法。
 *
 * @throws  SecurityException
 *          如果当前线程无法在指定的线程组中创建线程
 */
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (null, null, name)}具有相同的效果。
 *
 * @param   name
 *          新线程的名称
 */
public Thread(String name) {
    init(null, null, name, 0);
}

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (group, null, name)}具有相同的效果。
 *
 * @param  group
 *         线程组。如果为{@code null}并且存在安全管理器,则线程组由{@linkplain SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}确定。如果没有安全管理器或{@code SecurityManager.getThreadGroup()}返回{@code null},则线程组设置为当前线程的线程组。
 *
 * @param  name
 *         新线程的名称
 *
 * @throws  SecurityException
 *          如果当前线程无法在指定的线程组中创建线程
 */
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

/**
 * 分配一个新的{@code Thread}对象。这个构造函数与{@linkplain #Thread(ThreadGroup,Runnable,String) Thread} {@code (null, target, name)}具有相同的效果。
 *
 * @param  target
 *         当线程启动时,调用此线程的对象的{@code run}方法。如果为{@code null},则调用此线程的run方法。
 *
 * @param  name
 *         新线程的名称
 */
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

/**
 * 分配一个新的{@code Thread}对象,使其具有{@code target}作为其运行对象,具有指定的{@code name}作为其名称,并属于由{@code group}引用的线程组。
 *
 * <p>如果有安全管理器,则使用线程组作为参数调用其{@link SecurityManager#checkAccess(ThreadGroup) checkAccess}方法。
 *
 * <p>此外,当直接或间接由覆盖{@code getContextClassLoader}或{@code setContextClassLoader}方法的子类的构造函数调用时,将使用{@code RuntimePermission("enableContextClassLoaderOverride")}权限调用其{@code checkPermission}方法。
 *
 * <p>新创建的线程的优先级设置为创建它的线程的优先级,即当前运行的线程。可以使用{@linkplain #setPriority setPriority}方法将优先级更改为新值。
 *
 * <p>新创建的线程最初被标记为守护线程,当且仅当创建它的线程当前被标记为守护线程。可以使用{@linkplain #setDaemon setDaemon}方法更改线程是否为守护线程。
 *
 * @param  group
 *         线程组。如果为{@code null}并且存在安全管理器,则线程组由{@linkplain SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}确定。如果没有安全管理器或{@code SecurityManager.getThreadGroup()}返回{@code null},则线程组设置为当前线程的线程组。
 *
 * @param  target
 *         当线程启动时,调用此线程的对象的{@code run}方法。如果为{@code null},则调用此线程的run方法。
 *
 * @param  name
 *         新线程的名称
 *
 * @throws  SecurityException
 *          如果当前线程无法在指定的线程组中创建线程或无法覆盖上下文类加载器方法。
 */
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

其中,我们最经常使用的就是如下几个构造方法了。

public Thread() {
 init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
 init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
 init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
 init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
 init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
 init(group, target, name, 0);
}

通过Thread类的源码,我们可以看出,Thread类在进行初始化的时候,都是调用的init()方法,接下来,我们看看init()方法是个啥。

init()方法

Thread类中的构造方法是被创建Thread线程的线程调用的,此时,调用Thread的构造方法创建线程的线程就是父线程,在init()方法中,新创建的Thread线程会继承父线程的部分属性。


    /**
     * 初始化一个线程。
     *
     * @param g 线程组
     * @param target 被调用run()方法的对象
     * @param name 新线程的名称
     * @param stackSize 新线程的期望堆栈大小,或者为零表示忽略此参数。
     * @param acc 继承的AccessControlContext,如果为null,则使用AccessController.getContext()
     * @param inheritThreadLocals 如果为{@code true},则从构造线程继承可继承线程本地变量的初始值
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            // 判断是否为applet

            // 如果有安全管理器,询问安全管理器该怎么办
            if (security != null) {
                g = security.getThreadGroup();
            }

            // 如果安全管理器对此没有明确意见,则使用父线程组
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        // 无论是否显式传递了threadgroup,都要检查访问权限
        g.checkAccess();

        /*
         * 是否具有所需的权限?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        // 将指定的堆栈大小存储起来以备VM使用
        this.stackSize = stackSize;

        // 设置线程ID
        tid = nextThreadID();
    }

这个方法主要用于初始化Thread对象。下面是对代码的详细解释:

代码详细解释

  1. 设置线程名称
    1. 检查传入的name是否为null,如果是null,则抛出NullPointerException异常。
    2. 设置Thread对象的name为传入的name。
  2. 父线程设置
    1. 获取当前线程对象作为父线程。
  3. 安全校验
    1. 获取系统的安全管理器。
    2. 如果传入的线程组为null,则判断是否为applet。如果有安全管理器,则使用安全管理器的线程组。如果安全管理器对此没有明确意见,则使用父线程的线程组。
    3. 检查是否具有访问线程组的权限。
    4. 检查是否具有创建Thread子类对象的权限。
  4. 设置操作
    1. 将线程组设置为未启动状态。
    2. 设置线程的group为传入的线程组。
    3. 设置线程的daemon属性为父线程的daemon属性。
    4. 设置线程的priority为父线程的priority。
    5. 如果没有安全管理器或者父线程的contextClassLoader被子类覆盖,则将线程的contextClassLoader设置为父线程的contextClassLoader。否则,将线程的contextClassLoader设置为父线程的contextClassLoader。
    6. 设置线程的inheritedAccessControlContext为传入的AccessControlContext,如果为null,则设置为当前线程的AccessControlContext。
    7. 设置线程的target为传入的Runnable对象。
    8. 设置线程的priority为父线程的priority。
    9. 如果需要继承父线程的ThreadLocal变量,并且父线程的inheritableThreadLocals不为null,则创建一个继承父线程inheritableThreadLocals的map,并将其赋值给线程的inheritableThreadLocals。
    10. 将指定的堆栈大小存储起来以备VM使用。
    11. 设置线程的ID为下一个可用的线程ID。

run()方法

既然Thread类实现了Runnable接口,则Thread类就需要实现Runnable接口的run()方法,如下所示。

/**
     * 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法;
     * 否则,此方法不执行任何操作并返回。
     * <p>
     * Thread的子类应该重写此方法。
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

可以看到,Thread类中的run()方法实现非常简单,只是调用了Runnable对象的run()方法。所以,真正的任务是运行在run()方法中的。另外,需要注意的是:直接调用Runnable接口的run()方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用Thread类的start()方法。

start()方法

/**
     * 使此线程开始执行;Java虚拟机调用此线程的run方法。
     * <p>
     * 结果是两个线程同时运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。
     * <p>
     * 不允许多次启动一个线程。
     * 特别是,一旦线程完成执行,就不能重新启动它。
     *
     * @exception  IllegalThreadStateException  如果线程已经启动。
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * 对于由VM创建/设置的主线程或“系统”组线程,不会调用此方法。
         * 将来对此方法添加的任何新功能可能也必须添加到VM中。
         *
         * 零状态值对应于状态“NEW”。
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* 通知组,此线程即将启动,
         * 以便将其添加到组的线程列表中,
         * 并将组的未启动计数减一。 */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* 什么都不做。如果start0抛出了Throwable,
                  它将被传递到调用堆栈上 */
            }
        }
    }

从start()方法的源代码,我们可以看出:start()方法使用synchronized关键字修饰,说明start()方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。

这里,也是面试的一个坑:面试官:【问题一】能不能多次调用Thread类的start()方法来启动线程吗?【问题二】多次调用Thread线程的start()方法会发生什么?【问题三】为什么会抛出异常?

调用start()方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU执行),当有空闲的CPU时,这个线程就会被分配CPU来执行,此时线程的状态为运行状态,JVM会调用线程的run()方法执行任务。

sleep()方法

sleep()方法可以使当前线程休眠,其代码如下所示。

    /**
     * 使当前正在执行的线程休眠(临时停止执行)指定的毫秒数加上指定的纳秒数,取决于系统计时器和调度程序的精度和准确性。线程不会失去任何监视器的所有权。
     *
     * @param  millis
     *         以毫秒为单位的休眠时间长度
     *
     * @param  nanos
     *         {@code 0-999999} 补充的纳秒数
     *
     * @throws  IllegalArgumentException
     *          如果{@code millis}的值为负数,或者{@code nanos}的值不在范围{@code 0-999999}内
     *
     * @throws  InterruptedException
     *          如果任何线程中断了当前线程。抛出此异常时,当前线程的<i>中断状态</i>将被清除。
     */
    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);
    }

sleep()方法会让当前线程休眠一定的时间,这个时间通常是毫秒值,这里需要注意的是:调用sleep()方法使线程休眠后,线程不会释放相应的锁。

join()方法

join()方法会一直等待线程超时或者终止,代码如下所示。

/**
 * 等待最多{@code millis}毫秒,直到该线程终止。超时时间为{@code 0}表示永久等待。
 *
 * <p> 这个实现使用了一个基于{@code this.wait}调用和{@code this.isAlive}条件的循环。当一个线程终止时,会调用{@code this.notifyAll}方法。建议应用程序不要在{@code Thread}实例上使用{@code wait}、{@code notify}或{@code notifyAll}。
 *
 * @param  millis
 *         等待的时间(以毫秒为单位)
 *
 * @throws  IllegalArgumentException
 *          如果{@code millis}的值为负数
 *
 * @throws  InterruptedException
 *          如果任何线程中断了当前线程。抛出此异常时,当前线程的“中断状态”将被清除。
 */
public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("超时值为负数");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
  1. 首先,代码检查传入的等待时间是否为负数,如果是负数则抛出IllegalArgumentException异常。
  2. 然后,代码根据传入的等待时间进行不同的处理。如果等待时间为0,表示永久等待,代码会进入一个循环,只有当当前线程终止时才会退出循环。在循环中,使用wait(0)方法使线程等待,直到被唤醒。
  3. 如果等待时间不为0,代码进入另一个循环,该循环会在给定的时间内等待当前线程终止。在循环中,代码计算剩余的等待时间,并使用wait(delay)方法使线程等待指定的时间。然后,代码更新当前时间,直到等待时间超过给定的时间或当前线程终止。
  4. 最后,当当前线程终止时,代码退出循环,方法执行结束。

join()方法的使用场景往往是启动线程执行任务的线程,调用执行线程的join()方法,等待执行线程执行任务,直到超时或者执行线程终止。

interrupt()方法

interrupt()方法是中断当前线程的方法,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,可能会抛出InteruptedExeption异常,同时,会清除当前线程的中断状态。这种方式中断线程比较安全,它能使正在执行的任务执行能够继续执行完毕,而不像stop()方法那样强制线程关闭。代码如下所示。

/**
 * 中断这个线程。
 *
 * <p> 除非当前线程中断自己,这是始终允许的,否则将调用此线程的 {@link #checkAccess() checkAccess} 方法,
 * 这可能会导致抛出 {@link SecurityException}。
 *
 * <p> 如果此线程在 {@link Object} 类的 {@link Object#wait() wait()}、{@link Object#wait(long) wait(long)} 或
 * {@link Object#wait(long, int) wait(long, int)} 方法的调用中被阻塞,或者在此类的 {@link #join()}、{@link #join(long)}、
 * {@link #join(long, int)}、{@link #sleep(long)} 或 {@link #sleep(long, int)} 方法的调用中被阻塞,
 * 则它的中断状态将被清除,并且它将接收到一个 {@link InterruptedException}。
 *
 * <p> 如果此线程在对 {@link java.nio.channels.InterruptibleChannel InterruptibleChannel} 的 I/O 操作中被阻塞,
 * 则通道将被关闭,线程的中断状态将被设置,并且线程将接收到一个 {@link java.nio.channels.ClosedByInterruptException}。
 *
 * <p> 如果此线程在 {@link java.nio.channels.Selector} 中被阻塞,则线程的中断状态将被设置,并且它将立即从选择操作中返回,
 * 可能带有非零值,就像调用选择器的 {@link java.nio.channels.Selector#wakeup wakeup} 方法一样。
 *
 * <p> 如果不满足前面的任何条件,则将设置此线程的中断状态。
 *
 * <p> 中断一个不是活动状态的线程可能不会产生任何效果。
 *
 * @throws  SecurityException
 *          如果当前线程无法修改此线程
 *
 * @revised 6.0
 * @spec JSR-51
 */
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // 仅设置中断标志
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

这段代码实现了Java线程的中断功能。下面是其具体的实现流程:

  1. 首先,代码检查当前线程是否是自身。如果不是,就调用 checkAccess() 方法来检查当前线程是否有权限修改该线程。如果没有权限,则会抛出 SecurityException 异常。
  2. 然后,代码使用 synchronized 关键字来同步访问 blockerLock 对象。这是为了确保在中断线程时不会与其他线程发生冲突。
  3. 接下来,代码获取 blocker 对象的引用。blocker 对象是一个实现了 Interruptible 接口的对象,用于在线程被阻塞时执行特定的操作。
  4. 如果 blocker 对象不为空,说明线程正在被阻塞。此时,代码调用 interrupt0() 方法来设置中断标志,并调用 blocker.interrupt(this) 方法来通知 blocker 对象中断当前线程。然后,方法返回。
  5. 如果 blocker 对象为空,说明线程没有被阻塞。此时,代码直接调用 interrupt0() 方法来设置中断标志。

总之,这段代码实现了中断线程的功能。当线程被中断时,如果线程正在阻塞状态,则会通过 blocker 对象来中断线程;如果线程没有被阻塞,则直接设置中断标志。