一文讲尽Thread类的源码精髓

2023-05-27 0 976

责任编辑撷取自宏碁云街道社区《【高mammalian】Thread类的源码一脉相承-云街道社区-宏碁云》,译者:冰 河。

序言

前段时间和两个好友闲聊,他跟我讲起了他去XXX子公司复试的情形,辩手的两个难题把他打懵了!居然问他:你时常采用Thread建立缓存,那你看完Thread类的源码吗?我那个好友大自然是没看完Thread类的源码,接着,就没接着了!!!

因此,他们自学控制技术不但须要索韦泰,更须要博奈,那时,他们就一起来单纯看一看Thread类的源码。

特别注意:责任编辑是如前所述JDK 1.8来展开预测的。

Thread类的承继亲密关系

他们能采用右图来则表示Thread类的承继亲密关系。

一文讲尽Thread类的源码精髓

由上图他们能窥见,Thread类同时实现了RunnableUSB,而Runnable在JDK 1.8中被@FunctionalInterface注释记号为表达式式USB,RunnableUSB在JDK 1.8中的源码如下表所示右图。

@FunctionalInterface public interface Runnable { public abstract void run(); }

RunnableUSB的源码非常单纯,而已提供更多了两个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; //当前缓存的类加载器 privateClassLoader contextClassLoader;//访问控制上下文 private AccessControlContext inheritedAccessControlContext; //为匿名缓存生成名称的编号 private static intthreadInitNumber;//与此缓存相关的ThreadLocal,那个Map维护的是ThreadLocal类 ThreadLocal.ThreadLocalMap threadLocals = null; //与此缓存相关的ThreadLocal ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //当前缓存请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理 private long stackSize; //缓存终止后存在的JVM私有状态 private long nativeParkEventPointer; //缓存的id private long tid; //用于生成缓存id private static long threadSeqNumber; //当前缓存的状态,初始化为0,代表当前缓存还未启动 private volatile intthreadStatus =0; //由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置 //采用java.util.concurrent.locks.LockSupport.getBlocker访问 volatile Object parkBlocker; //InterruptibleUSB中定义了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; }

那个枚举类中的状态就代表了缓存生命周期的各状态。他们能采用右图来则表示缓存各个状态之间的转化亲密关系。

一文讲尽Thread类的源码精髓
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; //设置缓存idtid = nextThreadID(); }

Thread类中的构造方式是被建立Thread缓存的缓存调用的,此时,调用Thread的构造方式建立缓存的缓存就是父缓存,在init()方式中,新建立的Thread缓存会承继父缓存的部分属性。

run()方式

既然Thread类同时实现了RunnableUSB,则Thread类就须要同时实现RunnableUSB的run()方式,如下表所示右图。

@Override public void run() { if(target !=null) { target.run(); } }

能看到,Thread类中的run()方式同时实现非常单纯,而已调用了Runnable对象的run()方式。因此,真正的任务是运行在run()方式中的。另外,

须要特别注意的是:直接调用RunnableUSB的run()方式不会建立新缓存来执行任务,如果须要建立新缓存执行任务,则须要调用Thread类的start()方法。

start()方式

public synchronized void start() { //缓存不是初始化状态,则直接抛出异常 if (threadStatus != 0) throw newIllegalThreadStateException();//添加当前启动的缓存到缓存组 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()方式的源码,他们能窥见:

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) throwsInterruptedException{ 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 { longbase = 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()) { longdelay = millis – now;if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() – base; } } }public final synchronized void join(long millis, int nanos) throwsInterruptedException{ 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类上,因此,大家在学会采用的时候,一定要了解下底层的同时实现才好啊!

点击下方,第一时间了解宏碁云新鲜控制技术~

宏碁云博客_大数据博客_AI博客_云计算博客_开发者中心-宏碁云

#宏碁云开发者联盟#

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务