1.9、JDK 源码分析 - Thread

原创
2022/07/10 20:15
阅读数 255

摘要

在Java中,Thread类代表一个线程,本篇博文主要分享Thread相关的知识。

概述

在Java中,和线程密切相关的有Thread、Runable、还有java.util.concurrent并发包下面的相关类。此处我们主要介绍的是前面两个类;

一、Runable接口

任何用于给线程执行的实例,都应该实现Runnable接口。 该实例的类必须定义一个不带参数的方法run。

此接口旨在为那些希望在活动时执行代码的对象提供公共协议。例如,Runnable由类Thread实现。 处于活动状态仅仅意味着线程已经启动,但还没有停止。此外,Runnable提供了类在不子类化Thread的情况下处于活动状态的方法。实现Runnable的类可以在不子类化Thread的情况下运行,方法是实例化一个Thread实例,并将自身作为目标传入。

在大多数情况下,如果您只打算覆盖run()方法,而不覆盖其他Thread方法,那么应该使用Runnable接口。这一点很重要,因为除非程序员有意修改或增强类的基本行为,否则不应该子类化类。

@FunctionalInterface
public interface Runnable {
    /**
     * 当一个实现接口Runnable的对象被用来创建一个线程时,启动该线程会导致在单独执行的线程中调用对象的run方法。
     * run方法的一般约定是它可以采取任何动作。
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@FunctionalInterface是一个函数接口,里面没有任何方法,仅代表一个标记。

二、Thread类

Thread是Java中一个非常重要的类,在我们程序运行过程中,线程是必不过可少的。

了解该类的相关知识,对我们之后的编程都会非常有用。但是,这个类相对来说还是非常复杂的,总共2000多行代码。

(1)、Thread类定义

public class Thread implements Runnable {
    .....
}

从源码可以看到Thread类实现了Runnable接口,类访问权限public。

Thread类的类声明上面的注释有100行(详细阅读源码注释,对代码的理解非常重要),内容如下:

线程是程序中的执行线程。 Java虚拟机允许应用程序同时运行多个执行线程。每个线程都有优先级, 高优先级线程优先于低优先级线程执行。 每个线程可以也可以不被标记为守护进程。当运行在某个线程中的代码创建一个新的thread对象时,新线程的优先级最初被设置为与创建线程的优先级相等,并且当且仅当创建线程是一个守护线程时,它是一个守护线程。

当Java Virtual Machine启动时,通常有一个非守护线程(它通常调用某个指定类的main方法)。 Java虚拟机继续执行线程,直到出现以下情况:

  • 类Runtime的exit方法已经被调用,并且安全管理器已经允许进行退出操作。
  • 所有非守护线程的线程都已经死亡,无论是通过调用run方法返回,还是通过抛出一个传播到run方法之外的异常。

有两种方法可以创建新的执行线程。 一种是声明一个类是Thread的子类。 这个子类应该覆盖run方法的类Thread。 然后可以分配和启动子类的实例。 例如,一个线程计算比指定值大的质数,可以这样写:

     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接口的类。 然后该类实现run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。 另一种风格的相同示例如下:

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

每个线程都有一个用于标识的名称。 多个线程可以有相同的名称。 如果在创建线程时没有指定名称,则会为其生成一个新名称。除非另有说明,否则将null参数传递给该类中的构造函数或方法将引发NullPointerException异常。

参考:Runnable、Runtime#exit(int) 、Thread#run() 、Thread#stop()

(2)、本地方法注册

就是将Java定义的本地方法和系统或者C/C++编写的类库中的本地方法连接起来。此处,知识不清楚的小伙伴可参考<<JDK 源码分析 - Object 类>>这篇博文。

    //注册本地方法
    private static native void registerNatives();
    static {
        registerNatives();
    }
    //返回对当前执行的线程对象的引用。
    public static native Thread currentThread();
    /**
     * 提示调度器当前线程愿意放弃其当前对处理器的使用。 调度器可以随意忽略这个提示。
     * Yield是一种启发式尝试,用于改善相关程序直接的线程,否则会过度使用CPU. 它的使用应该与详细的概要分析和基准测试相结合,
     *以确保它实际上具有预期的效果。
     *
     * 很少适合使用这种方法。 它可能用于调试或测试目的,在这些目的中,它可能有助于重现由于竞争条件造成的bug。
     * 在设计并发控制结构(比如{@link java.util.concurrent.locks}时,它可能也很有用。
     */
    public static native void yield();

    /**
     * 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度器的精度和准确性。 线程不会失去任何锁(监视器)的所有权。
     * millis 睡眠的时间长度,以毫秒为单位
     * throws  IllegalArgumentException 如果{@code millis}的值是负的
     * throws  InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,清除当前线程的中断状态。
     */
    public static native void sleep(long millis) throws InterruptedException;

    private native void start0();
    //测试某个线程是否被中断。 中断状态是否重置取决于传递的ClearInterrupted的值。 
    private native boolean isInterrupted(boolean ClearInterrupted);
    //测试此线程是否活动。 如果线程已经启动并且还没有死亡,那么线程就是活的。 
    public final native boolean isAlive();
    /**
     * 当且仅当当前线程持有指定对象上的监视器锁时,返回 true。
     *
     * 这个方法被设计用来允许程序断言当前线程已经持有一个指定的锁: assert Thread.holdsLock(obj);
     *
     *  obj- 要测试锁所有权的对象
     * 如果obj为null,throws NullPointerException
     * 如果当前线程持有指定对象上的监视器锁。返回true
     */
    public static native boolean holdsLock(Object obj);

    private native static StackTraceElement[][] dumpThreads(Thread[] threads);
    private native static Thread[] getThreads();
    /* 一些私有辅助方法 */
    private native void setPriority0(int newPriority);//设置线程优先级
    private native void stop0(Object o);//停止线程
    private native void suspend0();//挂起线程
    private native void resume0();//恢复挂起的线程
    private native void interrupt0();//设置中断标记
    private native void setNativeName(String name);//设置名字

(3)、属性

    private volatile String name;//线程的名字
    private int            priority;//线程的优先级
    private Thread         threadQ;
    private long           eetop;
    private boolean     single_step;//是否单步执行此线程
    private boolean     daemon = false;//是否是守护线程,默认不是守护线程
    private boolean     stillborn = false;//JVM state
    private Runnable target;//需要完成的任务
    private ThreadGroup group;//线程组
    private ClassLoader contextClassLoader;//此线程的上下文类加载器
    private AccessControlContext inheritedAccessControlContext;//此线程的继承的访问控制上下文
    private static int threadInitNumber;//用于自动编号匿名线程。
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    ThreadLocal.ThreadLocalMap threadLocals = null;//属于当前线程的线程本地变量,这个map由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//当前线程可继承的线程本地变量,这个map由InheritableThreadLocal类维护
    private long stackSize;//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。 VM可以对这个数字做任何它想做的事情; 部分虚拟机会忽略。
    private long nativeParkEventPointer;//本机线程终止后任然存在,jvm私有状态
    private long tid;//线程ID
    private static long threadSeqNumber;//用于生成线程ID
    private volatile int threadStatus = 0;//Java线程状态,初始化以指示线程“尚未启动”
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
    //提供给当前调用java.util.concurrent.locks. locksupport.park的参数。 由(private) java.util.concurrent.locks.LockSupport.setBlocker设置,
    //通过java.util.concurrent.locks.LockSupport.getBlocker访问
    volatile Object parkBlocker;

    //在可中断的I / O操作中阻塞该线程的对象(如果有)。设置此线程的中断状态后,应调用阻塞程序的中断方法
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();

    /* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code
     */
    void blockedOn(Interruptible b) {
        synchronized (blockerLock) {
            blocker = b;
        }
    }
    public final static int MIN_PRIORITY = 1;//线程可以拥有的最低优先级。
    public final static int NORM_PRIORITY = 5;//分配给线程的默认优先级。
    public final static int MAX_PRIORITY = 10;//线程可以拥有的最大优先级。

(4)、构造方法

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    /**
     * Creates a new Thread that inherits the given AccessControlContext.
     * This is not a public constructor.
     */
    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);
    }

可以看到上面的构造方法都调用了一个init()方法,我们来看看init这个方法到底做了哪些事情;

(5)、init()私有方法

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    /**
     * 初始化一个线程。
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize 新线程所需的堆栈大小,或表示忽略此参数的0。
     * @param acc  继承的AccessControlContext , 或者如果为null,则AccessController.getContext()
     * @param inheritThreadLocals 如果{@code为真},则从构造线程继承可继承的线程局部变量的初始值
     */
    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 */
            /* 如果有SecurityManager,询问SecurityManager该怎么办*/
            if (security != null) {
                g = security.getThreadGroup();
            }
            /* 如果SecurityManager没有很强的意见,可以使用父线程组。*/
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        //检查访问,不管线程组是否显式传入
        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);
        /* 保存指定的堆栈大小,以防JVM关心 */
        this.stackSize = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }

从上面的源码可以了解到,线程创建新线程时,默认会继承父线程的可继承线程本地变量。

(6)、start启动方法

    /**
     * 使线程开始执行; Java虚拟机调用该线程的run方法。
     * 结果是两个线程并发运行:当前线程(调用start方法返回)和另一个线程(执行它的run方法)。
     * 一个线程启动一次以上是不合法的。尤其是线程一旦完成执行就不能重新启动。
     *
     * @exception  IllegalThreadStateException 如果线程已经启动。
     */
    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) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

(7)、run方法

    /**
     * 如果这个线程通过使用单独的Runnable 对象构造,那么Runnable对象的run方法将被调用
     * 否则, 这个方法不做任何事情,直接返回。
     * Thread子类应该重写该方法.
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

(8)、sleep方法

    /**
     * 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度器的精度和准确性。 线程不会失去任何锁(监视器)的所有权。
     * millis 睡眠的时间长度,以毫秒为单位
     * throws  IllegalArgumentException 如果{@code millis}的值是负的
     * throws  InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,清除当前线程的中断状态。
     */
    public static native void sleep(long millis) throws InterruptedException;
    /**
     * 使当前正在执行的线程休眠(暂时停止执行),休眠时间为指定的毫秒数加上指定的纳秒数,具体时间取决于系统计时器和调度器的精度。 
     * 线程不会失去任何监视器锁的所有权。
     * @param  millis 睡眠的时间长度,以毫秒为单位
     * @param  nanos  {@code 0-999999} 额外的纳秒睡眠时间
     * @throws  IllegalArgumentException 如果{@code millis}的值为负值,或者{@code nanos}的值不在{@code 0-999999}的范围内 
     * @throws  InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,当前线程的中断状态被清除。
     */
    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");
        }
        //如果500000<nanos <999999|| (nanos != 0 && millis == 0),则睡眠的毫秒数+1。
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
        sleep(millis);
    }

(9)、interrupt方法

    /**
     * 中断线程
     *
     * 除非当前线程总是被允许中断自己,否则会调用该线程的{@link #checkAccess() checkAccess}方法,这可能会引发{@link SecurityException}异常。
     *
     * 如果这个线程在调用{@link Object#wait() wait()}, {@link Object#wait(long) wait(long)},或{@link Object}类的{@link #wait(long, int) wait(long, int)}方法,      * 或该类的{@link #join()}, {@link #join(long, int)}, {@link #sleep(long)},或{@link #sleep(long, int)}方法时被阻塞,那么它的中断状态将被清除,
     * 并接收一个{@link InterruptedException}。
     *
     * 如果这个线程在I/O操作中被{@link java.nio.channels.InterruptibleChannel InterruptibleChannel}阻塞,那么通道将被关闭,线程的中断状态将被设置,
     * 并且线程将收到{@link java.nio.channels.ClosedByInterruptException}。
     *
     * 如果这个线程阻塞在{@link java.nio.channels.. Selector} 那么线程的中断状态将被设置,并且它将从选择操作中立即返回,可能带有一个非零的值,
     * 就像selector的{@link java.nio.channels. Selector#wakeup wakeup}方法被调用
     *
     * 如果前面的条件都不成立,那么这个线程的中断状态将被设置。 
     * 中断一个没有活动的线程不会产生任何影响。
     *
     * @throws  SecurityException  如果当前线程不能修改此线程
     */
    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();
    }

(10)、exit方法

    /**
     * 系统会调用这个方法,让线程在实际退出之前有机会进行清理。
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /*将线程的引用字段置空: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

(11)、setPriority方法

    /**
     * 更改此线程的优先级。
     * 首先不带参数调用这个线程的checkAccess方法。 这可能导致抛出一个SecurityException
     *
     * 否则,该线程的优先级被设置为指定的newPriority和该线程的线程组允许的最大优先级的较小值。
     *
     * @param newPriority 将线程的优先级设置为newPriority
     * @exception  IllegalArgumentException 如果优先级不在MIN_PRIORITY到MAX_PRIORITY范围内。
     * @exception  SecurityException 如果当前线程不能修改此线程。
     */
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

(12)、join方法

该方法底层使用Object对象的wait(long)方法来实现,如果相关知识不太熟悉,可参考<<JDK 源码分析 - Object 类>>

    /**
     * 等待最多{@code millis}毫秒这个线程死亡。 {@code 0}的超时意味着永远等待。
     *
     * 这个实现使用了根据条件{@code this.isAlive}循环调用{@code This.wait}。 当线程终止调用{@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("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;
            }
        }
    }

   //多个了一个纳秒的处理逻辑
    public final synchronized void join(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++;
        }

        join(millis);
    }
    //调用wait (0) 方法一直等待,直到获得锁或者被中断
    public final void join() throws InterruptedException {
        join(0);
    }

(13)、线程状态内部类State

    /**
     * 一个线程的状态。 线程可以处于下列状态之一:
     * NEW:尚未启动的线程处于这种状态。
     * RUNNABLE:在Java虚拟机中执行的线程就是这种状态。
     * BLOCKED:等待监视器锁而被阻塞的线程就处于这种状态。
     * WAITING:无限等待另一个线程执行特定操作的线程就处于这种状态。
     * TIMED_WAITING:在指定的等待时间内等待另一个线程执行操作的线程就是这种状态。
     * TERMINATED:已退出的线程处于这种状态。
     * 线程在给定的时间点只能处于一种状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
     */
    public enum State {
        /**
         *尚未启动的线程的线程状态。
         */
        NEW,

        /**
         * 可运行线程的线程状态。 处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(如处理器)的其他资源。
         */
        RUNNABLE,

        /**
         * 等待监视器锁的线程阻塞的线程状态。 一个处于阻塞状态的线程正在等待一个监视器锁进入一个同步的块/方法,
         * 或者在调用{@link Object#wait() Object.wait}之后重新进入一个同步的块/方法。
         */
        BLOCKED,

        /**
         * 等待线程的线程状态。
         * 由于调用以下方法之一,线程处于等待状态: 
         * <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>
         *
         * 处于等待状态的线程正在等待另一个线程执行特定的操作。
         */
        WAITING,

        /**
         * 具有指定等待时间的等待线程的线程状态。 线程处于定时等待状态,因为调用了下列方法之一,并指定了正的等待时间: 
         * <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,

        /**
         * 终止线程的线程状态。
         * 线程已完成执行。
         */
        TERMINATED;
    }

(14)、Java线程的状态关系

在操作系统层面,线程有五种状态:新建、就绪、运行、阻塞、死亡 在Java中,线程定义了六种状态:新建、就绪(包括就绪和运行两种状态)、等待、限时等待、阻塞、死亡

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部