当前位置:首页 > 教育 > 正文

做好Thread类源代码的精髓

前言

最近和一个工作了7年的朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread类创建线程,那你看过Thread类的源码吗?Thread类创建线程的流程是什么?如何中断一个正在执行的线程?我这个朋友平时觉得Thread类非常简单,自然是没看过Thread类的源码,然后,就没有然后了!!!

所以,我们学习技术不仅需要知其然,更需要知其所以然,今天,我们就一起来简单看看Thread类的源码。

注意:本文是基于JDK 1.8来进行分析的。

Thread类的继承关系

我们可以使用下图来表示Thread类的继承关系。

由上图我们可以看出,Thread类实现了Runnable接口,而Runnable在JDK 1.8中被@FunctionalInterface注解标记为函数式接口,Runnable接口在JDK 1.8中的源代码如下所示。

@FunctionalInterface

publicinterfaceRunnable{

publicabstractvoidrun;

Runnable接口的源码比较简单,只是提供了一个run方法,这里就不再赘述了。

接下来,我们再来看看@FunctionalInterface注解的源码,如下所示。

@Documented

展开全文

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public@interfaceFunctionalInterface {}

可以看到,@FunctionalInterface注解声明标记在Java类上,并在程序运行时生效。

Thread类的源码剖析Thread类定义

Thread在java.lang包下,Thread类的定义如下所示。

publicclassThreadimplementsRunnable{

加载本地资源

打开Thread类后,首先,我们会看到在Thread类的最开始部分,定义了一个静态本地方法registerNatives,这个方法主要用来注册一些本地系统的资源。并在静态代码块中调用这个本地方法,如下所示。

//定义registerNatives本地方法注册系统资源

privatestaticnativevoidregisterNatives;

static{

//在静态代码块中调用注册本地系统资源的方法

registerNatives;

Thread中的成员变量

Thread类中的成员变量如下所示。

//当前线程的名称

privatevolatileString name;

//线程的优先级

privateintpriority;

privateThread threadQ;

privatelongeetop;

//当前线程是否是单步线程

privatebooleansingle_step;

//当前线程是否在后台运行

privatebooleandaemon = false;

//Java虚拟机的状态

privatebooleanstillborn = false;

//真正在线程中执行的任务

privateRunnable target;

//当前线程所在的线程组

privateThreadGroup group;

//当前线程的类加载器

privateClassLoader contextClassLoader;

//访问控制上下文

privateAccessControlContext inheritedAccessControlContext;

//为匿名线程生成名称的编号

privatestaticintthreadInitNumber;

//与此线程相关的ThreadLocal,这个Map维护的是ThreadLocal类

ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程相关的ThreadLocal

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理

privatelongstackSize;

//线程终止后存在的JVM私有状态

privatelongnativeParkEventPointer;

//线程的id

privatelongtid;

//用于生成线程id

privatestaticlongthreadSeqNumber;

//当前线程的状态,初始化为0,代表当前线程还未启动

privatevolatileintthreadStatus = 0;

//由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置

//使用java.util.concurrent.locks.LockSupport.getBlocker访问

volatileObject parkBlocker;

//Interruptible接口中定义了interrupt方法,用来中断指定的线程

privatevolatileInterruptible blocker;

//当前线程的内部锁

privatefinalObject blockerLock = newObject;

//线程拥有的最小优先级

publicfinalstaticintMIN_PRIORITY = 1;

//线程拥有的默认优先级

publicfinalstaticintNORM_PRIORITY = 5;

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

publicfinalstaticintMAX_PRIORITY = 10;

从Thread类的成员变量,我们可以看出,Thread类本质上不是一个任务,它是一个实实在在的线程对象,在Thread类中拥有一个Runnable类型的成员变量target,而这个target成员变量就是需要在Thread线程对象中执行的任务。

线程的状态定义

在Thread类的内部,定义了一个枚举State,如下所示。

publicenumState {

//初始化状态

NEW,

//可运行状态,此时的可运行包括运行中的状态和就绪状态

RUNNABLE,

//线程阻塞状态

BLOCKED,

//等待状态

WAITING,

//超时等待状态

TIMED_WAITING,

//线程终止状态

TERMINATED;

这个枚举类中的状态就代表了线程生命周期的各状态。我们可以使用下图来表示线程各个状态之间的转化关系。

NEW:初始状态,线程被构建,但是还没有调用start方法。

RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。

BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。

WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。

TIME_WAITING:超时等待状态。可以在一定的时间自行返回。

TERMINATED:终止状态,当前线程执行完毕。

Thread类中的所有构造方法如下所示。

publicThread{

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

publicThread(Runnable target){

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

Thread(Runnable target, AccessControlContext acc) {

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

publicThread(ThreadGroup group, Runnable target){

init(group, target, "Thread-"+ nextThreadNum, 0);

publicThread(String name){

init( null, null, name, 0);

publicThread(ThreadGroup group, String name){

init(group, null, name, 0);

publicThread(Runnable target, String name){

init( null, target, name, 0);

publicThread(ThreadGroup group, Runnable target, String name){

init(group, target, name, 0);

publicThread(ThreadGroup group, Runnable target, String name,

longstackSize) {

init(group, target, name, stackSize);

其中,我们最经常使用的就是如下几个构造方法了。

publicThread{

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

publicThread(Runnable target){

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

publicThread(String name){

init( null, null, name, 0);

publicThread(ThreadGroup group, String name){

init(group, null, name, 0);

publicThread(Runnable target, String name){

init( null, target, name, 0);

publicThread(ThreadGroup group, Runnable target, String name){

init(group, target, name, 0);

通过Thread类的源码,我们可以看出,Thread类在进行初始化的时候,都是调用的init方法,接下来,我们看看init方法是个啥。

init方法privatevoidinit(ThreadGroup g, Runnable target, String name, longstackSize) {

init(g, target, name, stackSize, null, true);

privatevoidinit(ThreadGroup g, Runnable target, String name,

longstackSize, AccessControlContext acc,

booleaninheritThreadLocals) {

//线程的名称为空,抛出空指针异常

if(name == null) {

thrownewNullPointerException( "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方法,如下所示。

@Override

publicvoidrun{

if(target != null) {

target.run;

可以看到,Thread类中的run方法实现非常简单,只是调用了Runnable对象的run方法。所以,真正的任务是运行在run方法中的。另外, 需要注意的是:直接调用Runnable接口的run方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用Thread类的start方法。

start方法publicsynchronizedvoidstart{

//线程不是初始化状态,则直接抛出异常

if(threadStatus != 0)

thrownewIllegalThreadStateException;

//添加当前启动的线程到线程组

group.add( this);

//标记线程是否已经启动

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

privatenativevoidstart0;

从start方法的源代码,我们可以看出: start方法使用synchronized关键字修饰,说明start方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。

这里, 也是面试的一个坑:面试官:【问题一】能不能多次调用Thread类的start方法来启动线程吗?【问题二】多次调用Thread线程的start方法会发生什么?【问题三】为什么会抛出异常?

调用start方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU执行),当有空闲的CPU时,这个线程就会被分配CPU来执行,此时线程的状态为运行状态,JVM会调用线程的run方法执行任务。

sleep方法

sleep方法可以使当前线程休眠,其代码如下所示。

//本地方法,真正让线程休眠的方法

publicstaticnativevoidsleep( longmillis) throwsInterruptedException ;

publicstaticvoidsleep( longmillis, intnanos)

throwsInterruptedException {

if(millis < 0) {

thrownewIllegalArgumentException( "timeout value is negative");

if(nanos < 0|| nanos > 999999) {

thrownewIllegalArgumentException(

"nanosecond timeout value out of range");

if(nanos >= 500000|| (nanos != 0&& millis == 0)) {

millis++;

//调用本地方法

sleep(millis);

sleep方法会让当前线程休眠一定的时间,这个时间通常是毫秒值,这里需要注意的是: 调用sleep方法使线程休眠后,线程不会释放相应的锁。

join方法

join方法会一直等待线程超时或者终止,代码如下所示。

publicfinalsynchronizedvoidjoin( longmillis)

throwsInterruptedException {

longbase = System.currentTimeMillis;

longnow = 0;

if(millis < 0) {

thrownewIllegalArgumentException( "timeout value is negative");

if(millis == 0) {

while(isAlive) {

wait( 0);

} else{

while(isAlive) {

longdelay = millis - now;

if(delay <= 0) {

break;

wait(delay);

now = System.currentTimeMillis - base;

publicfinalsynchronizedvoidjoin( longmillis, intnanos)

throwsInterruptedException {

if(millis < 0) {

thrownewIllegalArgumentException( "timeout value is negative");

if(nanos < 0|| nanos > 999999) {

thrownewIllegalArgumentException(

"nanosecond timeout value out of range");

if(nanos >= 500000|| (nanos != 0&& millis == 0)) {

millis++;

join(millis);

publicfinalvoidjointhrowsInterruptedException {

join( 0);

join方法的使用场景往往是启动线程执行任务的线程,调用执行线程的join方法,等待执行线程执行任务,直到超时或者执行线程终止。

interrupt方法

interrupt方法是中断当前线程的方法,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,可能会抛出InteruptedExeption异常,同时,会清除当前线程的中断状态。这种方式中断线程比较安全,它能使正在执行的任务执行能够继续执行完毕,而不像stop方法那样强制线程关闭。代码如下所示。

publicvoidinterrupt{

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;

privatenativevoidinterrupt0;

总结

作为技术人员,要知其然,更要知其所以然,我那个朋友技术本身不错,各种框架拿来就用,基本没看过常用的框架源码和JDK中常用的API,属于那种CRUD型程序员,这次面试就栽在了一个简单的Thread类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!

往期推荐

12款开源的低代码开发平台,建议收藏 大厂裁员轰轰烈烈,哪个技术岗位可以独善其身? 全球开源技术峰会GOTC 2023将于4月在上海举办

这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦