工做三年,小胖连 Thread 源码都没看过?真的菜!

2021年02月24日 阅读数:9
这篇文章主要向大家介绍工做三年,小胖连 Thread 源码都没看过?真的菜!,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

金三银四,不少小伙伴都打算跳槽。而多线程是面试必问的,给你们分享下 Thread 源码解析,也算是我本身的笔记整理、思惟复盘。学习的同时,顺便留下点什么~前端

一、设置线程名

在使用多线程的时候,想要查看线程名是很简单的,调用 Thread.currentThread().getName() 便可。默认状况下,主线程名是 main,其余线程名是 Thread-x,x 表明第几个线程java

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

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

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

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

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


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 的值。编程

private volatile String name;

public final String getName() {
    return name;
}

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


public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

因此,咱们能够初始化时就指定线程名安全

public class MyThread implements Runnable {

    @Override
    public void run() {
        // 打印当前线程的名字
        System.out.println(Thread.currentThread().getName());
    }
}
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());
    }
}

二、线程优先级

在 Thread 源码中和线程优先级相关的属性有如下 3 个:微信

// 线程能够拥有的最小优先级
public final static int MIN_PRIORITY = 1;

// 线程默认优先级
public final static int NORM_PRIORITY = 5;

// 线程能够拥有的最大优先级
public final static int MAX_PRIORITY = 10

线程的优先级能够理解为线程抢占 CPU 时间片(也就是执行权)的几率,优先级越高概率越大,但并不意味着优先级高的线程就必定先执行。数据结构

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

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 中,咱们通常这样设置线程的优先级:

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

三、守护线程

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

它有两个特色:

  • 当别的非守护线程执行完了,虚拟机就会退出,守护线程也就会被中止掉。
  • 守护线程做为一个服务线程,没有服务对象就没有必要继续运行了

举个栗子:你能够把守护线程理解为公司食堂里面的员工,专门为办公室员工提供饮食服务,办公室员工下班回家了,它们也就都回家了。因此,不能使用守护线程访问资源(好比修改数据、进行I/O 操做等等),由于这货随时挂掉。反之,守护线程常常被用来执行一些后台任务,可是呢,你又但愿在程序退出时,或者说 JVM 退出时,线程可以自动关闭,此时,守护线程是你的首选

在 java 中,能够经过 setDaemon 能够设置守护线程,源码以下:

public final void setDaemon(boolean on) {
    // 判断是否有权限
    checkAccess();
    // 判断是否活跃
    if (isAlive()) {
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

从以上源码,能够知道必须在线程启动以前就把目标线程设置为守护线程,不然报错

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

public class DaemonThread implements Runnable {

    @Override
    public void run() {
        // 死循环
        while(true) {
          // 打印当前线程的名字
          System.out.println(Thread.currentThread().getName());
        }
    }

}

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

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

四、start() 和 run() 有啥区别?

首先从 Thread 源码来看,start () 方法属于 Thread 自身的方法,而且使用了 synchronized 来保证线程安全,源码以下:

public synchronized void start() {

        // 一、状态验证,不等于 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 抛出异常,则它将被传递到调用堆栈上
            }
        }
}

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

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 () 方法能够进行屡次调用,由于它是个普通方法

五、sleep 方法

sleep 方法的源码入下,它是个 native 方法。咱们无法看源码,只能经过注释来理解它的含义,我配上了简短的中文翻译,总结下来有三点注意:

  • 睡眠指定的毫秒数,且在这过程当中不释放锁
  • 若是参数非法,报 IllegalArgumentException
  • 睡眠状态下能够响应中断信号,并抛出 InterruptedException(后面会说)
  • 调用 sleep 方法,即会从 RUNNABLE 状态进入 Timed Waiting(计时等待)状态
/**
     * 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

// 二、若是参数非法,报 IllegalArgumentException
     
     * @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.

// 三、睡眠状态下能够响应中断信号,并抛出 InterruptedException

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

六、如何正确中止线程?

线程在不一样的状态下遇到中断会产生不一样的响应,有点会抛出异常,有的则没有变化,有的则会结束线程。

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

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

举个栗子:线程正在写入一个文件,这时收到终止信号,它就须要根据自身业务判断,是选择当即中止,仍是将整个文件写入成功后中止,而若是选择当即中止就可能形成数据不完整,无论是中断命令发起者,仍是接收者都不但愿数据出现问题。

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

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

 /**
     * Interrupts this thread.

一、只能本身中断本身,否则会抛出 SecurityException 

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

二、若是线程调用 wait、sleep、join 等方法,进入了阻塞,
会形成调用中断无效,抛 InterruptedException 异常。

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

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

     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>

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

     * <p> 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(),看看源码:

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

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

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

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

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

结果

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

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 () 判断线程是否被中断,随后检查是否还有工做要作。正确的中止线程写法应该是这样的:

while (!Thread.currentThread().islnterrupted() && more work to do) {
    do more work
}

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

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

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

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 语句,处于休眠中。

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 住异常而不处理是很是不友好的。这种行为叫屏蔽了中断请求

那怎么作才能避免这种状况呢?首先能够在方法签名中抛出异常,好比:

void subTask2() throws InterruptedException {
    Thread.sleep(1000);
}

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

除此之外,还能够在 catch 语句中再次中断线程。好比上述例子中,咱们能够在 catch 中这样写:

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

七、yield 方法

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

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

看代码注释知道:

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

好比:

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 方法很是不稳定。

八、join 方法

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

/**
 * 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;
    // 超时时间不能小于 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 种状态

福利

若是看到这里,喜欢这篇文章的话,请帮点个好看。微信搜索一个优秀的废人,关注后回复电子书送你 100+ 本编程电子书 ,不仅 Java 哦,详情看下图。回复 1024送你一套完整的 java 视频教程。

资源

C语言

C++

Java

Git

Python

GO

Linux

经典必读

面试相关

前端

人工智能

设计模式

数据库

数据结构与算法

计算机基础

一个优秀的废人