线程:Thread类源码解析(上)

mac2024-10-15  57

Thread类源码解析

1.基本架构类注释任务状态1)NEW2)RUNNABLE3)RUNNING4)BLOCKED5)TERMINATED6)WAITING和TIMED_WAITING 优先级守护线程ClassLoader 2. 多线程实现的两种方式继承Thread类及start方法源码实现Runnable及run方法源码Thread和Runnable之间的关系 3.初始化方法

1.基本架构

Thread类涉及较多基本概念,理解基本概念是读源码的前提。

类注释

每个线程都有优先级,高优先级的线程可能会优先执行父线程创建子线程后,子线程的优先级、是否为守护线程等属性与父线程是一致的JVM启动时,会将主线程执行,主线程是非守护线程。JVM在如下几种情况下才会停止,否则将持续运行 1)类运行时调用exit方法,且安全管理器已允许进行退出操作 2)全部非守护线程都已停止运行,无论是正常执行完成或是运行出错每个线程都有名字,多个线程可以具有相同的名字。如果构造方法中没有传入指定名称,则会自动生成

任务状态

目前比较多的是如下两个版本,

版本1 注意在RUNNABLE和RUNNING之间是双向箭头,当线程切换时,原本正在执行的线程从RUNNING状态转移至RUNNABLE状态。

版本2(目前应用更多)

任务状态说明,

1)NEW

线程对象刚创建完成,但是还没有运行。在new Thread()之后没有调用start方法之前的状态。

2)RUNNABLE

父线程调用子线程的start方法后,该线程对象才在JVM中挂号(JVM知道该线程要搞事)。版本1中的解释是此时处于可运行状态,但是还没有被CPU选中。版本2中则是调用start方法后,无论是否被CPU选中都称为RUNNABLE状态。

3)RUNNING

只出现在版本1中,一旦被CPU选中,才能执行线程的run方法,此时才算真正意义上的运行。版本2中RUNNING状态的转化

转入RUNNABLE状态 当CPU切换到其他线程时,正在运行的线程状态变为RUNNABLE,因为run方法没有在被执行转入BLOCKED状态 正在执行的线程调用了wait或sleep方法时,就会进入BLOCKED状态。区别是wait方法会放下当前CPU使用权,而sleep方法继续占用CPU转入TERMINATED状态 run方法执行完毕或意外终止。或者正在被执行的线程被调用了stop方法

4)BLOCKED

阻塞,由RUNNABLE或RUNNING进入BLOCKED的原因有,

线程等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED正在执行的线程调用了wait或sleep方法后就会进入BLOCKED状态(版本1中的解释)。在版本2中,执行了wait方法后,会进入到WAITING或TIMED_WAITING状态。

由BLOCKED进入RUNNABLE状态的时机,

阻塞操作结束,等待CPU再次选中的这段过程阻塞过程被打断,如其他线程调用了interrupt方法sleep方法设定的休眠时间结束后会回到RUNNABLE状态由于wait操作进入BLOCKED状态的线程,其他线程发出notify或notifyAll的信号时,会唤醒进入到RUNNABLE状态

线程在BLOCKED状态下如果被调用了stop方法时会转入TERMINATED状态。

5)TERMINATED

TERMINATED 状态意味着线程的生命周期已经走完。这是线程的终止状态。此状态的线程不会再转化为其它任何状态。

处于 RUNNING 或者 BLOCKED 状态的线程都有可能变为 TERMINATED 状态,但原因是类似的,如下:

线程运行正常结束线程运行异常终止JVM意外停止

6)WAITING和TIMED_WAITING

版本2中,遇到Object#wait、Thread#join、LockSupport#park这些方法时,线程会等待另一个线程完成特定动作之后才结束等待。二者的区别是TIMED_WAITING有设定等待时间。

注意,这几种状态并不是任务所有的状态,只是在 Java 源码中列举出了几种状态, Java 线程的处理方法都是围绕这几种状态的。

优先级

优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下,

// 最低优先级 public final static int MIN_PRIORITY = 1; // 普通优先级,也是默认的 public final static int NORM_PRIORITY = 5; // 最大优先级 public final static int MAX_PRIORITY = 10;

请注意优先级大仅仅是概率会更大,并不意味着就一定能够先于优先级低的获取。和摇车牌号一个道理,即使现在中签概率是标准的数倍,但摇中依然摇摇无期。而身边却时不时的出现第一次摇号就中的人。如果在 CPU 比较空闲的时候,那么优先级就没有用了。

守护线程

默认创建的线程都是非守护线程。

创建守护线程时,需要将 Thread 的 daemon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM 仍然会退出。

守护线程类似于餐厅清洁员,一直在默默地做打扫卫生的工作。这个工作相对独立,不需要和别的角色有什么交互。而当其他所有人都不工作了,也就没有工作的必要了,因为不会有新的垃圾产生。那么可以下班,餐厅也就关门了。

ClassLoader

ClassLoader可以简单理解成类加载器,就是把类从文件、二进制数组、URL 等位置加载成可运行 Class。

2. 多线程实现的两种方式

无返回值的线程初始化方式有两种,

继承Thread类及start方法源码

class MyThread extends Thread{ @Override public void run() { log.info(Thread.currentThread().getName()); } } @Test // 调用 start 方法即可,会自动调用到 run 方法的 public void extendThreadInit(){ new MyThread().start(); }

start方法源码如下,

public synchronized void start() { // 如果没有初始化,抛异常 if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); // started 是个标识符,动作发生之前标识符是 false,发生完成之后变成 true boolean started = false; try { // 这里会创建一个新的线程,执行完成之后,新的线程已经在运行了,既 target 的内容已经在运行了 start0(); // 这里执行的还是主线程 started = true; } finally { try { // 如果失败,把线程从线程组中删除 if (!started) { group.threadStartFailed(this); } // Throwable 可以捕捉一些 Exception 捕捉不到的异常,比如说子线程抛出的异常 } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } // 开启新线程使用的是 native 方法 private native void start0();

start方法主要逻辑如下,

检查线程状态,判断是否可启动将子线程加入到线程组中调用start0方法

start方法并不调用run方法,而是调用start0方法。start0方法是native方法,也称为JNI(Java Native Interface)方法。JNI方法是java和其它语言交互的方式。同样也是java代码和虚拟机交互的方式,虚拟机就是由C++和汇编所编写。

start0会进入JVM执行,run方法的执行在源码中无法找到,但是在start方法的注释中有这样一句话,

the Java Virtual Machine* calls the run method of this thread.

所以run方法的调用过程如下,

实现Runnable及run方法源码

Thread thread = new Thread(new Runnable() { @Override public void run() { log.info("{} begin run",Thread.currentThread().getName()); } }); // 开一个子线程去执行 thread.start(); // 不会新起线程,是在当前主线程上继续运行 thread.run();

这种就是实现 Runnable 的接口,并作为 Thread 构造器的入参。

调用时使用了两种方式,可以根据情况选择使用 start 或 run 方法,

使用 start 会开启子线程来执行 run 里面的内容使用 run 方法执行的还是主线程

run方法源码如下,

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

将Runnable对象传入Thread类中,调用run方法的过程,

Thread和Runnable之间的关系

看若兄弟,实为父子。Thread类的定义如下,

public class Thread implements Runnable

Thread实现了Runnable接口。

Thread类的run方法上有@Override注解。所以继承thread类实现多线程,其实也相当于是实现runnable接口的run方法。此时,不需要再传入一个Runnable类去启动。它自己已具备了thread的功能,自己就可以运转起来。

Thread类也实现了Runnable接口,那么Thread子类对象也可以传入另外的Thread对象,让其执行自己的run方法。

3.初始化方法

源码关键部分如下,

// 无参构造器,线程名字自动生成 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } // g 代表线程组,线程组可以对组内的线程进行批量的操作 // target 是Runnable对象 // name 线程的名字,自定义或自动生成 // stackSize 可以设置堆栈的大小 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name.toCharArray(); // 创建Thread对象的线程作为父线程 Thread parent = currentThread(); this.group = g; // 子线程会继承父线程的守护属性 this.daemon = parent.isDaemon(); // 子线程继承父线程的优先级属性 this.priority = parent.getPriority(); // classLoader 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); // 当父线程的 inheritableThreadLocals 的属性值不为空时 // 会把 inheritableThreadLocals 里面的值全部传递给子线程 if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; /* Set thread ID */ // 线程 id 自增 tid = nextThreadID(); }
最新回复(0)