大家好,我是冰河~~
最近和一个朋友聊天,他跟我说起了他去 XXX 公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用 Thread 创建线程,那你看过 Thread 类的源码吗?我这个朋友自然是没看过 Thread 类的源码,然后,就没有然后了!!!
所以,我们学习技术不仅需要知其然,更需要知其所以然,今天,我们就一起来简单看看 Thread 类的源码。
注意:本文是基于 JDK 1.8 来进行分析的。
Thread 类的继承关系
我们可以使用下图来表示 Thread 类的继承关系。
由上图我们可以看出,Thread 类实现了 Runnable 接口,而 Runnable 在 JDK 1.8 中被 @FunctionalInterface 注解标记为函数式接口,Runnable 接口在 JDK 1.8 中的源代码如下所示。
 @FunctionalInterfacepublic interface Runnable {    public abstract void run();}
   复制代码
 
Runnable 接口的源码比较简单,只是提供了一个 run()方法,这里就不再赘述了。
接下来,我们再来看看 @FunctionalInterface 注解的源码,如下所示。
 @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface FunctionalInterface {}
   复制代码
 
可以看到,@FunctionalInterface 注解声明标记在 Java 类上,并在程序运行时生效。
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;//与此线程相关的ThreadLocalThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理private long stackSize;//线程终止后存在的JVM私有状态private long nativeParkEventPointer;//线程的idprivate long tid;//用于生成线程idprivate 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 类的构造方法
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);}
   复制代码
 
其中,我们最经常使用的就是如下几个构造方法了。
 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()方法
 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;    Thread parent = currentThread();    //获取系统安全管理器    SecurityManager security = System.getSecurityManager();    //线程组为空    if (g == null) {        //获取的系统安全管理器不为空        if (security != null) {            //从系统安全管理器中获取一个线程分组            g = security.getThreadGroup();        }        //线程分组为空,则从父线程获取        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);    /* Stash the specified stack size in case the VM cares */    this.stackSize = stackSize;
    //设置线程id    tid = nextThreadID();}
   复制代码
 
Thread 类中的构造方法是被创建 Thread 线程的线程调用的,此时,调用 Thread 的构造方法创建线程的线程就是父线程,在 init()方法中,新创建的 Thread 线程会继承父线程的部分属性。
run()方法
既然 Thread 类实现了 Runnable 接口,则 Thread 类就需要实现 Runnable 接口的 run()方法,如下所示。
 @Overridepublic void run() {    if (target != null) {        target.run();    }}
   复制代码
 
可以看到,Thread 类中的 run()方法实现非常简单,只是调用了 Runnable 对象的 run()方法。所以,真正的任务是运行在 run()方法中的。另外,需要注意的是:直接调用 Runnable 接口的 run()方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用 Thread 类的 start()方法。
start()方法
 public synchronized void start() {   //线程不是初始化状态,则直接抛出异常    if (threadStatus != 0)        throw new IllegalThreadStateException();    //添加当前启动的线程到线程组    group.add(this);  //标记线程是否已经启动    boolean started = false;    try {        //调用本地方法启动线程        start0();        //将线程是否启动标记为true        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 */        }    }}
private native void start0();
   复制代码
 
从 start()方法的源代码,我们可以看出:<font color="##FF0000">start()方法使用 synchronized 关键字修饰,说明 start()方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。
这里,也是面试的一个坑:面试官:【问题一】能不能多次调用 Thread 类的 start()方法来启动线程吗?【问题二】多次调用 Thread 线程的 start()方法会发生什么?【问题三】为什么会抛出异常?
调用 start()方法后,新创建的线程就会处于就绪状态(如果没有分配到 CPU 执行),当有空闲的 CPU 时,这个线程就会被分配 CPU 来执行,此时线程的状态为运行状态,JVM 会调用线程的 run()方法执行任务。
sleep()方法
sleep()方法可以使当前线程休眠,其代码如下所示。
 //本地方法,真正让线程休眠的方法public static native void sleep(long millis) 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");    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {        millis++;    }  //调用本地方法    sleep(millis);}
   复制代码
 
sleep()方法会让当前线程休眠一定的时间,这个时间通常是毫秒值,这里需要注意的是:调用 sleep()方法使线程休眠后,线程不会释放相应的锁。
join()方法
join()方法会一直等待线程超时或者终止,代码如下所示。
 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);}
public final void join() throws InterruptedException {    join(0);}
   复制代码
 
join()方法的使用场景往往是启动线程执行任务的线程,调用执行线程的 join()方法,等待执行线程执行任务,直到超时或者执行线程终止。
interrupt()方法
interrupt()方法是中断当前线程的方法,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,可能会抛出 InteruptedExeption 异常,同时,会清除当前线程的中断状态。这种方式中断线程比较安全,它能使正在执行的任务执行能够继续执行完毕,而不像 stop()方法那样强制线程关闭。代码如下所示。
 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();}private native void interrupt0();
   复制代码
 总结
作为技术人员,要知其然,更要知其所以然,我那个朋友技术本身不错,各种框架拿来就用,基本没看过常用的框架源码和 JDK 中常用的 API,属于那种 CRUD 型程序员,这次面试就栽在了一个简单的 Thread 类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!
好了,今天就到这儿吧,我是冰河,我们下期见~~
评论