【高并发】朋友去面试竟然栽在了Thread类的源码上
大家好,我是冰河~~
前言
最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread创建线程,那你看过Thread类的源码吗?我这个朋友自然是没看过Thread类的源码,然后,就没有然后了!!!
(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>)
所以,我们学习技术不仅需要知其然,更需要知其所以然,今天,我们就一起来简单看看Thread类的源码。
注意:本文是基于JDK 1.8来进行分析的。
Thread类的继承关系
我们可以使用下图来表示Thread类的继承关系。
由上图我们可以看出,Thread类实现了Runnable接口,而Runnable在JDK [email protected],Runnable接口在JDK 1.8中的源代码如下所示。
@FunctionalInterface public interface Runnable { public abstract void run(); }
Runnable接口的源码比较简单,只是提供了一个run()方法,这里就不再赘述了。
接下来,[email protected],如下所示。
@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; //与此线程相关的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 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()方法,如下所示。
@Override public 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()方法的源代码,我们可以看出: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类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!
推荐阅读
《实践出真知:全网最强秒杀系统架构解密,不是所有的秒杀都是秒杀!!》
《注意:线程的执行顺序与你想的并不一样!!》
《深入解析Callable接口 》
《两种异步模型与深度解析Future接口!》
《解密SimpleDateFormat类的线程安全问题和六种解决方案!》
《不得不说的线程池与ThreadPoolExecutor类浅析》
《深度解析线程池中那些重要的顶层接口和抽象类》
《从源码角度分析创建线程池究竟有哪些方式》
《通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的》
《通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程》
《通过源码深度分析线程池中Worker线程的执行流程》
《从源码角度深度解析线程池是如何实现优雅退出的》
《ScheduledThreadPoolExecutor与Timer的区别和简单示例》
《深度解析ScheduledThreadPoolExecutor类的源代码》
《由InterruptedException异常引发的思考》
《浅谈AQS中的CountDownLatch、Semaphore与CyclicBarrier》
《浅谈AQS中的ReentrantLock、ReentrantReadWriteLock、StampedLock与Condition》
好了,今天就到这儿吧,我是冰河,我们下期见~~
你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:
相关文章
- Jgit的使用笔记
- 利用Github Action实现Tornadofx/JavaFx打包
- 叹息!GitHub Trending 即将成为历史!
- 微软软了?开源社区讨论炸锅,GitHub CEO 亲自来答
- GitHub Trending 列表频现重复项,前后端都没去重?
- Photoshop Elements 2021版本软件安装教程(mac+windows全版本都有)
- (ps全版本)Photoshop 2020的安装与破解教程(mac+windows全版本都有)
- (ps全版本)Photoshop cc2018的安装与破解教程(mac+windows全版本,包括2023
- 环境搭建:Oracle GoldenGate 大数据迁移到 Redshift/Flat file/Flume/Kafka测试流程
- 每个开发人员都要掌握的:最小 Linux 基础课
- 来撸羊毛了!Windows 环境下 Hexo 博客搭建,并部署到 GitHub Pages
- 超实用!手把手入门 MongoDB:这些坑点请一定远离
- 【GitHub日报】22-10-09 zustand、neovim、webtorrent、express 等4款App今日上新
- 【GitHub日报】22-10-10 brew、minio、vite、seaweedfs、dbeaver 等8款App今日上新
- 【GitHub日报】22-10-11 cobra、grafana、vue、ToolJet、redwood 等13款App今日上新
- Photoshop 2018 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2017 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2020 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2023 资源免费下载(mac+windows全版本都有,包括最新的2023)
- 最新版本Photoshop CC2018软件安装教程(mac+windows全版本都有,包括2023