• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

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

互联网 diligentman 2周前 (02-23) 9次浏览

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

1、设置线程名

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

我们点进去构造方法,看看它是怎么命名的:调用了 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());
    }
}

2、线程优先级

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

3、守护线程

守护线程是低优先级的线程,专门为其他线程服务的,其他线程执行完了,它也就挂了。在 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 也不再执行

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

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

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

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 () 方法其实就是此线程要执行的业务方法,源码如下:

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.IllegalStateExc++eption;而 run () 方法可以进行多次调用,因为它是个普通方法

5、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.

// 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
     *          <i>interrupted status</i> 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 怎么使用,先来看看源码(已经给了清晰的注释):

 /**
     * Interrupts this thread.

1、只能自己中断自己,不然会抛出 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.

2、如果线程调用 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.

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

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

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

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

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

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

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

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

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

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 源码都没看过?真的菜!

看到这里,有人可能说老子中断了个寂寞???正确的中断写法应该是这样的:我们通过 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());
    }
}

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

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

BLOCKED

首先,启动线程1、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 一样,能响应中断。

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

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 状态

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

仅仅 c++atch 异常就够了吗?

实际开发中往往是团队协作,互相调用。我们的方法中调用了 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();
}

运行结果:

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

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

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

7、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 方法非常不稳定。

8、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 状态)是会改变线程状态的。最后,上一张完成版的线程状态切换图

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

送点福利

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


程序员灯塔
转载请注明原文链接:工作三年,小胖连 Thread 源码都没看过?真的菜!
喜欢 (0)