前言

最近和一个工作了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
public 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;
//与此线程相关的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();elsethis.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类实现了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();//将线程是否启动标记为truestarted = true;} finally {try {//线程未启动成功if (!started) {//将线程在线程组里标记为启动失败group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit 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 flagb.interrupt(this);return;}}//调用本地方法中断线程interrupt0();
}
private native void interrupt0();

总结

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

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

【高并发】一个工作了7年的朋友去面试竟然被Thread类虐的体无完肤相关推荐

  1. 多线程与高并发基础一(超发--多线程悲观锁,乐观锁、类数据库悲观锁乐观锁)

    PS:看完文章后对自己以前所做过的并发和锁机制有了深入原理的了解. 知其然和知其所以然! 遂以记之! 关键词: 线程,同步,单例,高并发,高访问,死锁 一.大规模并发带来的挑战 在过去的工作中,我曾经 ...

  2. JAVA高并发服务器工作笔记0001---Java中InetAddress与InetSocketAddress的基本用法

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 一.InetAddress:类的主要作用是封装IP及DNS,因为这个类没有构造器,所以我们要用他的 ...

  3. 【高并发】又一个朋友面试栽在了Thread类的stop()方法和interrupt()方法上!

    来自:冰河技术 写在前面 新一轮的面试已经过去,可能是疫情的原因吧,很多童鞋纷纷留言说今年的面试题难度又提高了,尤其是对并发编程的知识.我细想了下,也许有那么点疫情的原因吧,但无论面试的套路怎么变,只 ...

  4. mysql每秒支持多少并发_如何设计一个高并发系统?

    面试题 如何设计一个高并发系统? 面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先. 如果你确 ...

  5. oom 如何避免 高并发_【面试题】如何设计一个高并发系统?

    面试题 如何设计一个高并发系统? 原文链接:https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/high- ...

  6. 面试题:如何设计一个高并发系统?

    面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先. 如果你确实有真才实学,在互联网公司里干过高 ...

  7. 高并发面试 - 如何设计一个高并发系统?

    高并发面试 - 如何设计一个高并发系统? 面试题 如何设计一个高并发系统? 面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里 ...

  8. 如何设计一个高并发系统架构

    1,一般一个高并发系统的架构组成如下图所示: 2,面试题 如何设计一个高并发系统? 3.面试官心里分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘 ...

  9. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

最新文章

  1. SSI —— 开源的人类行为分析解决方案
  2. 计算机网络或计算机基础知识点滴1
  3. php图片上传报502,PHPStrom上传文件报502错误原因,_PHP教程
  4. 【转】Android图片加载神器之Fresco-加载图片基础[详细图解Fresco的使用]
  5. CCKS 2018 | 最佳论文:南京大学提出 DSKG,将多层 RNN 用于知识图谱补全
  6. CSS基础必备知识点03
  7. 教你手工检测SQL注入
  8. C 标准库 limits.h
  9. css3仿天气风车旋转
  10. CIA的海外间谍,要如何完美避开AI的监控?
  11. Bootstrap学习(一):Bootstrap简介
  12. mysql 5.6 bug_MySQL 5.6的一个bug引发的故障
  13. python如何执行代码漏洞_在漏洞利用Python代码真的很爽
  14. 手机木马以及移动安全
  15. VS2013 下载地址
  16. Linux命令之火车来了
  17. STM32L152 的参考电压Vrefint输出
  18. 32怎么通过一个按键实现不同工作模式_游戏工作室防封IP,免费领!!!魔兽世界怀旧版独享IP免费送...
  19. c# 实现金山词霸一样的屏幕取词
  20. Linux下CP命令的使用!

热门文章

  1. linux+软盘启动程序,软盘上的Linux系统方案
  2. 总线的数据传输类型(微机接口技术)
  3. CSS中的字体属性和使用
  4. 关于学习Python的一点学习总结(38)
  5. Codeforces Round #601 (Div. 2)D. Feeding Chicken,二维压一维,几何平面的分配
  6. P3382 【模板】三分法,难度⭐⭐⭐
  7. ER图转为关系模式(超详细,超简单)
  8. Excel百万数据导入oracle,excel表数据导入oracle的方法!(超级有用)
  9. C++中构造函数调用构造函数
  10. 初版python计算器