一文讲尽Thread类的源码精髓

一文,thread,源码,精髓 · 浏览次数 : 70

小编点评

# Thread类介绍 Thread类是用于执行线程的类,它继承自同步类,意味着它可以同步执行方法。 * `start()`方法:用于启动线程,它会设置线程的中断标志位,使线程处于等待执行状态。 * `join()`方法:用于等待线程执行任务,它会阻塞当前线程直到线程执行任务完成或者线程终止。 * `interrupt()`方法:用于中断当前线程,它会设置线程的中断标志位,使线程处于等待执行状态。 **Thread类主要属性:** * `currentThread`:当前线程的实例。 * `state`:线程状态。 * `interruptFlags`:中断标志位。 * `lock`:线程锁。 **Thread类主要方法:** * `start()` * `join()` * `interrupt()` **线程示例:** ```java public class ThreadExample { public static void main(String[] args) { ThreadExample thread = new ThreadExample(); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` **运行结果:** ``` java thread state is suspended thread interrupted state is resumed ``` **总结:** Thread类是用于执行线程的类,它可以同步执行方法,并支持线程中断机制。 **线程示例代码:** ```java public class ThreadExample { public static void main(String[] args) { ThreadExample thread = new ThreadExample(); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` **注意:** * `start()`方法会设置线程的中断标志位,使线程处于等待执行状态。 * `join()`方法会阻塞当前线程直到线程执行任务完成或者线程终止。 * `interrupt()`方法用于中断当前线程,它会设置线程的中断标志位,使线程处于等待执行状态。

正文

摘要:今天,我们就一起来简单看看Thread类的源码。

本文分享自华为云社区《【高并发】Thread类的源码精髓》,作者:冰 河。

前言

最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用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();
 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类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!

 

点击关注,第一时间了解华为云新鲜技术~

与一文讲尽Thread类的源码精髓相似的内容:

一文讲尽Thread类的源码精髓

摘要:今天,我们就一起来简单看看Thread类的源码。 本文分享自华为云社区《【高并发】Thread类的源码精髓》,作者:冰 河。 前言 最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread创建线程,那你看过Thread类的源码吗?我

[转帖]一文看尽 JVM GC 调优

https://zhuanlan.zhihu.com/p/428731068 首先看一个著名的学习方法论 向橡皮鸭求助学会提问,提问也是一门艺术提问前,先投入自己的时间做好功课发生了什么事情问题的基本情况你投入的研究和发现能正确提出你的问题,你的问题差不多已经解决一半深入的思考你的问题,大多情况下,

一文讲透产品经理如何用好ChatGPT

4.0版本推出后,在中文互联网上并没有辅助产品经理工作的详细介绍。因此,我基于GPT-4,梳理了帮助产品经理全流程提效的方法,整理了一些prompt,本文旨在分享这些收获,希望能抛砖引玉。

详解事务模式和Lua脚本,带你吃透Redis 事务

摘要:Redis事务包含两种模式:事务模式和Lua脚本。 本文分享自华为云社区《一文讲透 Redis 事务》,作者: 勇哥java实战分享。 准确的讲,Redis事务包含两种模式:事务模式和Lua脚本。 先说结论: Redis的事务模式具备如下特点: 保证隔离性; 无法保证持久性; 具备了一定的原子

RocketMQ消费者是如何负载均衡的

摘要:RocketMQ 支持两种消息模式:集群消费( Clustering )和广播消费( Broadcasting )。 本文分享自华为云社区《一文讲透RocketMQ消费者是如何负载均衡的》,作者:勇哥java实战分享。 RocketMQ 支持两种消息模式:集群消费( Clustering )和

[转帖]Nginx Ingress 高并发实践

概述 Nginx Ingress Controller 基于 Nginx 实现了 Kubernetes Ingress API,Nginx 是公认的高性能网关,但如果不对其进行一些参数调优,就不能充分发挥出高性能的优势。之前我们在 Nginx Ingress on TKE 部署最佳实践 一文中讲了

【RocketMQ】【源码】消息拉模式分析

RocketMQ有两种获取消息的方式,分别为推模式和拉模式。 **推模式** 推模式在[【RocketMQ】消息的拉取](https://www.cnblogs.com/shanml/p/16463964.html)一文中已经讲过,虽然从名字上看起来是消息到达Broker后推送给消费者,实际上还是需

一文全懂:Linux磁盘分区

本篇文章讲了linux磁盘管理中的分区相关知识,mbr和gpt分区有何不同?从添加一块硬盘到最后挂载到系统,经历了哪些步骤?如何创建交换分区并给交换分区扩容?

一文详解ATK Loss论文复现与代码实战

摘要:该方法的主要思想是使用数值较大的排在前面的梯度进行反向传播,可以认为是一种在线难例挖掘方法,该方法使模型讲注意力放在较难学习的样本上,以此让模型产生更好的效果。 本文分享自华为云社区《ATK Loss论文复现与代码实战》,作者:李长安。 损失是一种非常通用的聚合损失,其可以和很多现有的定义在单

【RocketMQ】消息的拉取总结

在上一讲中,介绍了消息的存储,生产者向Broker发送消息之后,数据会写入到CommitLog中,这一讲,就来看一下消费者是如何从Broker拉取消息的。 RocketMQ消息的消费以组为单位,有两种消费模式: 广播模式:同一个消息队列可以分配给组内的每个消费者,每条消息可以被组内的消费者进行消费。