万字长文,Thread 类源码解析!

admin 9320

Thread 源码解析金九银十,很多小伙伴都打算跳槽。而多线程是面试必问的,给大家分享下 Thread 源码解析,也算是我自己的笔记整理、思维复盘。学习的同时,顺便留下点什么~

1、设置线程名在使用多线程的时候,想要查看线程名是很简单的,调用 Thread.currentThread ().getName () 即可。默认情况下,主线程名是 main,其他线程名是 Thread-x,x 代表第几个线程。

我们点进去构造方法,看看它是怎么命名的:调用了 init 方法,init 方法内部调用了 nextThreadNum 方法。

代码语言:javascript代码运行次数:0运行复制public Thread() {

init(null, null, "Thread-" + nextThreadNum(), 0);

}

nextThreadNum 是一个线程安全的方法,同一时间只可能有一个线程修改,而 threadInitNumber 是一个静态变量,它可以被类的所有对象访问。所以,每个线程在创建时直接 +1 作为子线程后缀。

代码语言:javascript代码运行次数:0运行复制/* For autonumbering anonymous threads. */

private static int threadInitNumber;

private static synchronized int nextThreadNum() {

return threadInitNumber++;

}

再看 init 方法,注意到最后有 this.name = name 赋值给 volatile 变量的 name,默认就是用 Thread-x 作为子线程名。

代码语言:javascript代码运行次数:0运行复制

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {

init(g, target, name, stackSize, null, 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;

// 省略代码

}

最终 getName 方法拿到的就是这个 volatile 变量 name 的值。

代码语言:javascript代码运行次数:0运行复制private volatile String name;

public final String getName() {

return name;

}

注意到源码中,有带 name 参数的构造方法:

代码语言:javascript代码运行次数:0运行复制

public Thread(Runnable target, String name) {

init(null, target, name, 0);

}

所以,我们可以初始化时就指定线程名

代码语言:javascript代码运行次数:0运行复制public class MyThread implements Runnable {

@Override

public void run() {

// 打印当前线程的名字

System.out.println(Thread.currentThread().getName());

}

}

代码语言:javascript代码运行次数:0运行复制public class TestMain {

public static void main(String[] args) {

MyThread myThread = new MyThread();

//带参构造方法给线程起名字

Thread thread1 = new Thread(myThread, "一个优秀的废人");

Thread thread2 = new Thread(myThread, "在复习多线程");

// 启动线程

thread1.start();

thread2.start();

// 打印当前线程的名字

System.out.println(Thread.currentThread().getName());

}

}

2、线程优先级在 Thread 源码中和线程优先级相关的属性有以下 3 个:

代码语言:javascript代码运行次数:0运行复制// 线程可以拥有的最小优先级

public final static int MIN_PRIORITY = 1;

// 线程默认优先级

public final static int NORM_PRIORITY = 5;

// 线程可以拥有的最大优先级

public final static int MAX_PRIORITY = 10

线程的优先级可以理解为线程抢占 CPU 时间片(也就是执行权)的概率,优先级越高几率越大,但并不意味着优先级高的线程就一定先执行。

Thread 类中,设置优先级的源码如下:

代码语言:javascript代码运行次数:0运行复制public final void setPriority(int newPriority) {

ThreadGroup g;

checkAccess();

// 先验证优先级的合理性,不能大于 10,也不能小于 1

if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {

throw new IllegalArgumentException();

}

if((g = getThreadGroup()) != null) {

// 优先级如果超过线程组的最高优先级,则把优先级设置为线程组的最高优先级(有种一人得道鸡犬升天的感觉~)

if (newPriority > g.getMaxPriority()) {

newPriority = g.getMaxPriority();

}

// native 方法

setPriority0(priority = newPriority);

}

}

在 java 中,我们一般这样设置线程的优先级:

代码语言:javascript代码运行次数:0运行复制public class TestMain {

public static void main(String[] args) {

MyThread myThread = new MyThread();

//带参构造方法给线程起名字

Thread thread1 = new Thread(myThread, "一个优秀的废人");

Thread thread2 = new Thread(myThread, "在复习多线程");

// 设置优先级

thread1.setPriority(1);

thread2.setPriority(10);

// 启动线程

thread1.start();

thread2.start();

// 打印当前线程的名字

System.out.println(Thread.currentThread().getName());

}

}

3、守护线程守护线程是低优先级的线程,专门为其他线程服务的,其他线程执行完了,它也就挂了。在 java 中,我们的垃圾回收线程就是典型的守护线程。

它有两个特点:

当别的非守护线程执行完了,虚拟机就会退出,守护线程也就会被停止掉。守护线程作为一个服务线程,没有服务对象就没有必要继续运行了。举个栗子:你可以把守护线程理解为公司食堂里面的员工,专门为办公室员工提供饮食服务,办公室员工下班回家了,它们也就都回家了。所以,不能使用守护线程访问资源(比如修改数据、进行 I/O 操作等等),因为这货随时挂掉。反之,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。

在 java 中,可以通过 setDaemon 可以设置守护线程,源码如下:

代码语言:javascript代码运行次数:0运行复制public final void setDaemon(boolean on) {

// 判断是否有权限

checkAccess();

// 判断是否活跃

if (isAlive()) {

throw new IllegalThreadStateException();

}

daemon = on;

}

从以上源码,可以知道必须在线程启动之前就把目标线程设置为守护线程,否则报错。

例子:新增一个 DaemonThread,里面执行的任务是死循环不断打印自己的线程名字。

代码语言:javascript代码运行次数:0运行复制public class DaemonThread implements Runnable {

@Override

public void run() {

// 死循环

while(true) {

// 打印当前线程的名字

System.out.println(Thread.currentThread().getName());

}

}

}

测试:在启动之前先把 thread2 设置为守护线程,thread1 启动,再启动 thread2 。

代码语言:javascript代码运行次数:0运行复制public class TestMain {

public static void main(String[] args) {

MyThread myThread = new MyThread();

DaemonThread daemonThread = new DaemonThread();

//带参构造方法给线程起名字

Thread thread1 = new Thread(myThread, "一个优秀的废人");

Thread thread2 = new Thread(daemonThread, "在复习多线程");

// 设置 thread2 为守护线程

thread2.setDaemon(true);

// 启动线程

thread1.start();

thread2.start();

// 打印当前线程的名字

System.out.println(Thread.currentThread().getName());

}

}

正常来说,如果 thread2 不是守护线程,JVM 不会退出,除非发生严重的异常,thread2 会一直死循环在控制台打印自己的名字。然而,设置为守护线程之后,JVM 退出,thread2 也不再执行:

守护线程.png

4、start () 和 run () 有啥区别?首先从 Thread 源码来看,start () 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全,源码如下:

代码语言:javascript代码运行次数:0运行复制public synchronized void start() {

// 1、状态验证,不等于 NEW 的状态会抛出异常

if (threadStatus != 0)

throw new IllegalThreadStateException();

// 2、通知线程组,此线程即将启动

group.add(this);

boolean started = false;

try {

start0();

started = true;

} finally {

try {

if (!started) {

group.threadStartFailed(this);

}

} catch (Throwable ignore) {

// 3、不处理任何异常,如果 start0 抛出异常,则它将被传递到调用堆栈上

}

}

}

而 run () 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run () 方法其实就是此线程要执行的业务方法,源码如下:

代码语言:javascript代码运行次数:0运行复制public class Thread implements Runnable {

// 忽略其他方法......

private Runnable target;

@Override

public void run() {

if (target != null) {

target.run();

}

}

}

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

关于两者区别这个问题,其实上次写多线程的开篇,已经说过了,有兴趣的戳:这里长话短说,它的区别是:

run 方法里面定义的是线程执行的任务逻辑,直接调用跟普通方法没啥区别 start 方法启动线程,使线程由 NEW 状态转为 RUNNABLE,然后再由 jvm 去调用该线程的 run () 方法去执行任务start 方法不能被多次调用,否则会抛出 java.lang.IllegalStateException;而 run () 方法可以进行多次调用,因为它是个普通方法5、sleep 方法sleep 方法的源码入下,它是个 native 方法。我们没法看源码,只能通过注释来理解它的含义,我配上了简短的中文翻译,总结下来有三点注意:

睡眠指定的毫秒数,且在这过程中不释放锁如果参数非法,报 IllegalArgumentException睡眠状态下可以响应中断信号,并抛出 InterruptedException(后面会说)调用 sleep 方法,即会从 RUNNABLE 状态进入 Timed Waiting(计时等待)状态代码语言:javascript代码运行次数:0运行复制/**

* 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.

// 1、睡眠指定的毫秒数,且在这过程中不释放锁

* @param millis

* the length of time to sleep in milliseconds

*

* @throws IllegalArgumentException

* if the value of {@code millis} is negative

// 2、如果参数非法,报 IllegalArgumentException

* @throws InterruptedException

* if any thread has interrupted the current thread. The

* interrupted status of the current thread is

* cleared when this exception is thrown.

// 3、睡眠状态下可以响应中断信号,并抛出 InterruptedException

*/

public static native void sleep(long millis) throws InterruptedException;

6、如何正确停止线程?线程在不同的状态下遇到中断会产生不同的响应,有点会抛出异常,有的则没有变化,有的则会结束线程。

如何正确停止线程?有人说这不简单嘛。直接 stop 方法,stop 方法强制终止线程,所以它是不行的。它已经被 Java 设置为 @Deprecated 过时方法了。

主要原因是 stop 太暴力了,没有给线程足够的时间来处理在线程停止前保存数据的逻辑,任务就停止了,会导致数据完整性的问题。

举个栗子:线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。

一般情况下,使用 interrupt 方法来请求停止线程,它并不是直接停止。它仅仅是给这个线程发了一个信号告诉它,它应该要结束了 (明白这一点非常重要!),而要不要马上停止,或者过一段时间后停止,甚至压根不停止都是由被停止的线程根据自己的业务逻辑来决定的。

要了解 interrupt 怎么使用,先来看看源码(已经给了清晰的注释):

代码语言:javascript代码运行次数:0运行复制 /**

* Interrupts this thread.

1、只能自己中断自己,不然会抛出 SecurityException

*

Unless the current thread is interrupting itself, which is

* always permitted, the {@link #checkAccess() checkAccess} method

* of this thread is invoked, which may cause a {@link

* SecurityException} to be thrown.

2、如果线程调用 wait、sleep、join 等方法,进入了阻塞,

会造成调用中断无效,抛 InterruptedException 异常。

*

If this thread is blocked in an invocation of the {@link

* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link

* Object#wait(long, int) wait(long, int)} methods of the {@link Object}

* class, or of the {@link #join()}, {@link #join(long)}, {@link

* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},

* methods of this class, then its interrupt status will be cleared and it

* will receive an {@link InterruptedException}.

*

*

If this thread is blocked in an I/O operation upon an {@link

* java.nio.channels.InterruptibleChannel InterruptibleChannel}

* then the channel will be closed, the thread's interrupt

* status will be set, and the thread will receive a {@link

* java.nio.channels.ClosedByInterruptException}.

*

*

If this thread is blocked in a {@link java.nio.channels.Selector}

* then the thread's interrupt status will be set and it will return

* immediately from the selection operation, possibly with a non-zero

* value, just as if the selector's {@link

* java.nio.channels.Selector#wakeup wakeup} method were invoked.

3、以上三种情况都不会发生时,才会把线程的中断状态改变

*

If none of the previous conditions hold then this thread's interrupt

* status will be set.

4、中断已经挂了的线程是无效的

*

Interrupting a thread that is not alive need not have any effect.

*

* @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) {

// 判断是不是阻塞状态的线程调用,比如刚调用 sleep()

Interruptible b = blocker;

if (b != null) {

interrupt0(); // Just to set the interrupt flag

// 如果是,抛异常同时推出阻塞。将中断标志位改为 false

b.interrupt(this);

return;

}

}

// 否则,顺利改变标志位

interrupt0();

}

interrupt 方法提到了四个点:

只能自己中断自己,不然会抛出 SecurityException如果线程调用 wait、sleep、join 等方法进入了阻塞,会造成调用中断无效,抛 InterruptedException 异常。以上情况不会发生时,才会把线程的中断状态改变中断已经挂了的线程是无效的除此以外,java 中跟中断有关的方法还有 interrupted() 和 isInterrupted(),看看源码:

代码语言:javascript代码运行次数:0运行复制/**

* Tests whether the current thread has been interrupted. The

* interrupted status 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).

*

*

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 true if the current thread has been interrupted;

* false otherwise.

* @see #isInterrupted()

* @revised 6.0

*/

public static boolean interrupted() {

return currentThread().isInterrupted(true);

}

/**

* Tests whether this thread has been interrupted. The interrupted

* status of the thread is unaffected by this method.

*

*

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 true if this thread has been interrupted;

* false otherwise.

* @see #interrupted()

* @revised 6.0

*/

public boolean isInterrupted() {

return isInterrupted(false);

}

/**

* 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);

两个点:

isInterrupted () 用于判断中断标志位,调用不会影响当前标志位interrupted () 用于清除中断标志位,调用会清除标志位前面说了,**interrupt 只是发个信号给线程,视线程状态把它的中断标志位设为 true 或者清除(设置为 false),那它会改变线程状态吗?** 前文《线程的状态》说过线程有 6 种状态,我们来验证每种状态的中断响应以及状态变更情况:

NEW & TERMINATED代码语言:javascript代码运行次数:0运行复制public class StopThread implements Runnable {

@Override

public void run() {

// do something

}

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new StopThread());

System.out.println(thread.isInterrupted());

}

}

运行结果:线程并没启动,标志不生效

结果

代码语言:javascript代码运行次数:0运行复制public class StopThread implements Runnable {

@Override

public void run() {

// do something

}

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new StopThread());

thread.start();

thread.join();

System.out.println(thread.getState());

thread.interrupt();

System.out.println(thread.isInterrupted());

}

}

运行结果:线程已挂,标志并不生效

结果

RUNNABLE代码语言:javascript代码运行次数:0运行复制public class StopThread implements Runnable {

@Override

public void run() {

int count = 0;

while (true) {

if (count < 10) {

System.out.println(count++);

}

}

}

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new StopThread());

thread.start();

// 查看状态

System.out.println(thread.getState());

thread.interrupt();

// 等待 thread 中断

Thread.sleep(500);

// 查看标志位

System.out.println(thread.isInterrupted());

// 查看状态

System.out.println(thread.getState());

}

}

运行结果:仅仅设置中断标志位,JVM 并没有退出,线程还是处于 RUNNABLE 状态。

结果

看到这里,有人可能说老子中断了个寂寞???正确的中断写法应该是这样的:我们通过 Thread.currentThread ().isInterrupt () 判断线程是否被中断,随后检查是否还有工作要做。正确的停止线程写法应该是这样的:

代码语言:javascript代码运行次数:0运行复制while (!Thread.currentThread().islnterrupted() && more work to do) {

do more work

}

在 while 中,通过 Thread.currentThread ().isInterrupt () 判断线程是否被中断,随后检查是否还有工作要做。&& 表示只有当两个判断条件同时满足的情况下,才会去执行线程的任务。实际例子:

代码语言:javascript代码运行次数:0运行复制public class StopThread implements Runnable {

@Override

public void run() {

int count = 0;

while (!Thread.currentThread().isInterrupted() && count < 1000) {

System.out.println("count = " + count++);

}

System.out.println("响应中断退出线程");

}

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new StopThread());

thread.start();

// 查看状态

System.out.println(thread.getState());

// 中断

thread.interrupt();

// 查看标志位

System.out.println(thread.isInterrupted());

// 等待 thread 中断

Thread.sleep(500);

// 查看标志位

System.out.println(thread.isInterrupted());

// 查看状态

System.out.println(thread.getState());

}

}

结果

我的业务是从 0 开始计数,大于 1000 或者线程接收到中断信号就停止计数。调用 interrupt ,该线程检测到中断信号,中断标记位就会被设置成 true,于是在还没打印完 1000 个数的时候就会停下来。这样就不会有安全问题。这种就属于通过 interrupt 正确停止线程的情况

BLOCKED首先,启动线程 1、2,调用 synchronized 修饰的方法,thread1 先启动占用锁,thread2 将进入 BLOCKED 状态。

代码语言:javascript代码运行次数:0运行复制public class StopDuringSleep implements Runnable {

public synchronized static void doSomething(){

while(true){

//do something

}

}

@Override

public void run() {

doSomething();

}

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(new StopDuringSleep());

thread1.start();

Thread thread2 = new Thread(new StopDuringSleep());

thread2.start();

Thread.sleep(1000);

System.out.println(thread1.getState());

System.out.println(thread2.getState());

thread2.interrupt();

System.out.println(thread2.isInterrupted());

System.out.println(thread2.getState());

}

}

运行结果:跟 RUNNABLE 一样,能响应中断。

结果

sleep 期间(WAITING 状态)能否感受到中断?上面讲 sleep 方法时说过, sleep 是可以响应马上中断信号,并清除中断标志位(设置为 false),同时抛出 InterruptedException 异常,退出计时等待状态。看看例子:主线程休眠 5 毫秒后,通知子线程中断,此时子线程仍在执行 sleep 语句,处于休眠中。

代码语言:javascript代码运行次数:0运行复制public class StopDuringSleep implements Runnable {

@Override

public void run() {

int count = 0;

try {

while (!Thread.currentThread().isInterrupted() && count < 1000) {

System.out.println("count = " + count++);

// 子线程 sleep

Thread.sleep(1000000);

}

} catch (InterruptedException e) {

// 判断该线程的中断标志位状态

System.out.println(Thread.currentThread().isInterrupted());

// 打印线程状态

System.out.println(Thread.currentThread().getState());

e.printStackTrace();

}

}

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new StopDuringSleep());

thread.start();

// 主线程 sleep

Thread.sleep(5);

thread.interrupt();

}

}

运行结果:interrupt 会把处于 WAITING 状态线程改为 RUNNABLE 状态

运行结果

仅仅 catch 异常就够了吗?实际开发中往往是团队协作,互相调用。我们的方法中调用了 sleep 或者 wait 等能响应中断的方法时,仅仅 catch 住异常而不处理是非常不友好的。这种行为叫屏蔽了中断请求。

那怎么做才能避免这种情况呢?首先可以在方法签名中抛出异常,比如:

代码语言:javascript代码运行次数:0运行复制void subTask2() throws InterruptedException {

Thread.sleep(1000);

}

Java 中,异常肯定是有调用方处理的。调用方要么自己抛到上层,要么 try catch 处理。如果每层逻辑都遵守规范,将中断信号传递到顶层,最终让 run () 方法可以捕获到异常。虽然 run 方法本身没有抛出 checkedException 的能力,但它可以通过 try/catch 根据业务逻辑来处理异常。

除此以外,还可以在 catch 语句中再次中断线程。比如上述例子中,我们可以在 catch 中这样写:

代码语言:javascript代码运行次数:0运行复制try {

// 省略代码

} catch (InterruptedException e) {

// 判断该线程的中断标志位状态

System.out.println(Thread.currentThread().isInterrupted());

// 打印线程状态

System.out.println(Thread.currentThread().getState());

// 再次中断

Thread.currentThread().interrupt();

// 判断该线程的中断标志位状态

System.out.println(Thread.currentThread().isInterrupted());

e.printStackTrace();

}

运行结果:

运行结果

sleep 期间被中断,会清除中断信号将其置为 false。这时就需要手动在 catch 中再次设置中断信号。如此,中断信号依然可以被检测,后续方法仍可知道这里发生过中断,并做出相应逻辑处理。

结论:NEW 和 TERMINATED 状态的线程不响应中断,其他状态可响应;同时 interrupt 会把 WAITING & TimeWAITING 状态的线程改为 RUNNABLE

7、yield 方法看 Thread 的源码可以知道 yield () 为本地方法,也就是说 yield () 是由 C 或 C++ 实现的,源码如下:

代码语言:javascript代码运行次数:0运行复制/**

* 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.

*

*

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.

*

*

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();

看代码注释知道:

当前线程调用 yield () 会让出 CPU 使用权,给别的线程执行,但是不确保真正让出。谁先抢到 CPU 谁执行。当前线程调用 yield () 方法,会将状态从 RUNNABLE 转换为 WAITING。比如:

代码语言:javascript代码运行次数:0运行复制public static void main(String[] args) throws InterruptedException {

Runnable runnable = new Runnable() {

@Override

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println("线程:" +

Thread.currentThread().getName() + " I:" + i);

if (i == 5) {

Thread.yield();

}

}

}

};

Thread t1 = new Thread(runnable, "T1");

Thread t2 = new Thread(runnable, "T2");

t1.start();

t2.start();

}

执行这段代码会发现,每次的执行结果都不一样。那是因为 yield 方法非常不稳定。

8、join 方法调用 join 方法,会等待该线程执行完毕后才执行别的线程。按照惯例,先来看看源码:

代码语言:javascript代码运行次数:0运行复制/**

* Waits at most {@code millis} milliseconds for this thread to

* die. A timeout of {@code 0} means to wait forever.

*

*

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

* interrupted status 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;

// 超时时间不能小于 0

if (millis < 0) {

throw new IllegalArgumentException("timeout value is negative");

}

// 等于 0 表示无限等待,直到线程执行完为之

if (millis == 0) {

// 判断子线程 (其他线程) 为活跃线程,则一直等待

while (isAlive()) {

wait(0);

}

} else {

// 循环判断

while (isAlive()) {

long delay = millis - now;

if (delay <= 0) {

break;

}

wait(delay);

now = System.currentTimeMillis() - base;

}

}

}

从源码知道几点:

从源码中可以看出 join () 方法底层还是通过 wait () 方法来实现的。当前线程终止,会调用当前实例的 notifyAll 方法唤醒其他线程。调用 join 方法,会使当前线程从 RUNNABLE 状态转至 WAITING 状态。终结Thread 类中主要有 start、run、sleep、yield、join、interrupt 等方法,其中 start、sleep、yield、join、interrupt(改变 sleep 状态)是会改变线程状态的。最后,上一张完成版的线程状态切换图:

线程的 6 种状态

巨人的肩膀https://kaiwu.lagou.com/course/courseInfo.htm?courseId=16#/detail/pc?id=239