老猿说说-Thread

article/2025/8/18 10:24:19

引导语

从本章开始我们开始学习线程的知识,线程是非常有趣的一个章节,大多数同学对于线程 API,属于不用就忘,到用时需要百度的情况,希望通过本小节的源码阅读,能够加深对线程的印象。

本小节主要三章,本章主要说线程的基本概念、使用姿势、Thread 和 Runnable 的源码;Future、ExecutorService 源码解析章节主要说异步线程执行;押宝线程源码面试题章节主要说说常遇到的源码面试题。

由于线程的概念很多,所以本章会先介绍很多线程的基本概念,说清楚后再解析源码,不然有些同学会看不懂,大家见谅。

1 类注释

1.1 Thread

  1. 每个线程都有优先级,高优先级的线程可能会优先执行;

  2. 父线程创建子线程后,优先级、是否是守护线程等属性父子线程是一致的;

  3. JVM 启动时,通常都启动 MAIN 非守护线程,以下任意一个情况发生时,线程就会停止:
    退出方法被调用,并且安全机制允许这么做(比如调用 Thread.interrupt 方法);
    所有非守护线程都消亡,或者从运行的方法正常返回,或者运行的方法抛出了异常;

  4. 每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字。

2 线程的基本概念

我们接下来介绍一下线程的基本概念:

2.1 线程的状态

网上有各种介绍线程状态的文章,我们这里说线程的状态是从源码的角度,源码中一共列举了六种状态,如下图:
link
我们解析一下这个图:

  1. NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程的状态都是 NEW;
  2. 当我们运行 strat 方法,子线程被创建成功之后,子线程的状态变成 RUNNABLE,RUNNABLE 表示线程正在运行中;
  3. 子线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED,TERMINATED 表示线程已经运行结束了;
  4. 如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED,BLOCKED 表示阻塞的意思;
  5. WAITING 和 TIMED_WAITING 类似,都表示在遇到 Object#wait、Thread#join、LockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结束等待,只不过 TIMED_WAITING 是带有等待时间的(可以看下面的 join 方法的 demo)。

再次重申,这 6 种状态并不是线程所有的状态,只是在 Java 源码中列举出的 6 种状态, Java 线程的处理方法都是围绕这 6 种状态的。

2.2 优先级

优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 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;

2.3 守护线程

我们默认创建的线程都是非守护线程。创建守护线程时,需要将 Thread 的 daemon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM 仍然会退出,我们在工作中,可能会写一些工具做一些监控的工作,这时我们都是用守护子线程去做,这样即使监控抛出异常,但因为是子线程,所以也不会影响到业务主线程,因为是守护线程,所以 JVM 也无需关注监控是否正在运行,该退出就退出,所以对业务不会产生任何影响。

2.4 ClassLoader

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

3 线程两种初始化方式

无返回值的线程主要有两种初始化方式:

3.1 继承 Thread,成为 Thread 的子类

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

上述代码打印出的线程名称是:Thread-0,而主线程的名字是:Thread [main,5,main],由此可见,的确是开了一个子线程来执行打印的操作。

我们一起来看下 start 的底层源码:

// 该方法可以创建一个新的线程出来
public synchronized void start() {// 如果没有初始化,抛异常if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);// started 是个标识符,我们在做一些事情的时候,经常这么写// 动作发生之前标识符是 false,发生完成之后变成 trueboolean 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 thenit will be passed up the call stack */}}
}
// 开启新线程使用的是 native 方法
private native void start0();

3.2 实现 Runnable 接口,作为 Thread 的入参

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

这种就是实现 Runnable 的接口,并作为 Thread 构造器的入参,我们调用时使用了两种方式,可以根据情况选择使用 start 或 run 方法,使用 start 会开启子线程来执行 run 里面的内容,使用 run 方法执行的还是主线程。

我们来看下 run 方法的源码:

// 简单的运行,不会新起线程,target 是 Runnable
public void run() {if (target != null) {target.run();}
}

源码中的 target 就是在 new Thread 时,赋值的 Runnable。

4 线程初始化

线程初始化的源码有点长,我们只看比较重要的代码 (不重要的被我删掉了),如下:

// 无参构造器,线程名字自动生成
public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}
// g 代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
// target 是我们要运行的对象
// name 我们可以自己传,如果不传默认是 "Thread-" + nextThreadNum(),nextThreadNum 方法返回的是自增的数字
// 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 parent = currentThread();this.group = g;// 子线程会继承父线程的守护属性this.daemon = parent.isDaemon();// 子线程继承父线程的优先级属性this.priority = parent.getPriority();// classLoaderif (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);// 当父线程的 inheritableThreadLocals 的属性值不为空时// 会把 inheritableThreadLocals 里面的值全部传递给子线程if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);this.stackSize = stackSize;/* Set thread ID */// 线程 id 自增tid = nextThreadID();
}

从初始化源码中可以看到,很多属性,子线程都是直接继承父线程的,包括优先性、守护线程、inheritableThreadLocals 里面的值等等。

5 线程其他操作

5.1 join

join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作,我们写了一个 demo,如下:

@Test
public void join() throws Exception {Thread main = Thread.currentThread();log.info("{} is run。",main.getName());Thread thread = new Thread(new Runnable() {@Overridepublic void run() {log.info("{} begin run",Thread.currentThread().getName());try {Thread.sleep(30000L);} catch (InterruptedException e) {e.printStackTrace();}log.info("{} end run",Thread.currentThread().getName());}});// 开一个子线程去执行thread.start();// 当前主线程等待子线程执行完成之后再执行thread.join();log.info("{} is end", Thread.currentThread());
}

执行的结果,就是主线程在执行 thread.join (); 代码后会停住,会等待子线程沉睡 30 秒后再执行,这里的 join 的作用就是让主线程等待子线程执行完成,我们画一个图示意一下:
link
从图中可以看出,主线程一直等待子线程沉睡 30s 后才继续执行,在等待期间,主线程的状态也是 TIMED_WAITING。

5.2 yield

yield 是个 native 方法,底层代码如下:

public static native void yield();

意思是当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程过度使用 cpu,我们在写 while 死循环的时候,预计短时间内 while 死循环可以结束的话,可以在循环里面使用 yield 方法,防止 cpu 一直被 while 死循环霸占。

有点需要说明的是,让步不是绝不执行,重新竞争时,cpu 也有可能重新选中自己。

5.3 sleep

sleep 也是 native 方法,可以接受毫秒的一个入参,也可以接受毫秒和纳秒的两个入参,意思是当前线程会沉睡多久,沉睡时不会释放锁资源,所以沉睡时,其它线程是无法得到锁的。

接受毫秒和纳秒两个入参时,如果给定纳秒大于等于 0.5 毫秒,算一个毫秒,否则不算。

5.4 interrupt

interrupt 中文是打断的意思,意思是可以打断中止正在运行的线程,比如:

Object#wait ()、Thread#join ()、Thread#sleep (long) 这些方法运行后,线程的状态是 WAITING 或 TIMED_WAITING,这时候打断这些线程,就会抛出 InterruptedException 异常,使线程的状态直接到 TERMINATED;
如果 I/O 操作被阻塞了,我们主动打断当前线程,连接会被关闭,并抛出 ClosedByInterruptException 异常;
我们举一个例子来说明如何打断 WAITING 的线程,代码如下:

@Test
public void testInterrupt() throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {log.info("{} begin run",Thread.currentThread().getName());try {log.info("子线程开始沉睡 30 s");Thread.sleep(30000L);} catch (InterruptedException e) {log.info("子线程被打断");e.printStackTrace();}log.info("{} end run",Thread.currentThread().getName());}});// 开一个子线程去执行thread.start();Thread.sleep(1000L);log.info("主线程等待 1s 后,发现子线程还没有运行成功,打断子线程");thread.interrupt();
}

例子主要说的是,主线程会等待子线程执行 1s,如果 1s 内子线程还没有执行完,就会打断子线程,子线程被打断后,会抛出 InterruptedException 异常,执行结束,运行的结果如下图:
link

6 总结

本章主要介绍了线程的基本概念、状态、无返回值线程的初始化方式和线程的常用操作,这些知识也是工作中常用的,也是大家都必须了解的,为后面的学习打下基础。


http://chatgpt.dhexx.cn/article/60fETk0E.shtml

相关文章

先进计算成趋势,猿代码助力科学之梦|猿代码科技

计算,是社会发展各个环节都存在的名词,无论是科学研究,或者是生产制造,甚至于每天的睁眼闭眼,只要有思考就会有计算。在全球经济一体化的当下,经济社会对于“计算”的依赖日益凸显,“计算”早已…

猿辅导获国际科研大奖:深度解析它为何在科研界“混得”风生水起

近年来,随着新一代人工智能在全球范围内的蓬勃兴起,人们的生产和生活方式也发生了巨大的变化。人工智能作为引领新一轮科技革命和产业变革的战略性技术之一,正在各领域落地应用。为了进一步推动人工智能与教育深度融合,发挥人工智…

猿来如此java系统_2020年最新 猿来如此springboot基础脚手架(资料完整)

『视频截图』: 98.jpg (78.82 KB, 下载次数: 0) 2020-5-29 10:44 上传 97.jpg (20.2 KB, 下载次数: 0) 2020-5-29 10:43 上传 『课程目录』: 目录:/6、2020年最新 springboot基础脚手架(笔记、源码、素材 完整) ┣━━课堂笔记 ┃ ┗━━springboot基础脚手架课堂…

猿来绘Java-31-为什么子类重写的方法抛出的异常类型小于等于父类被重写的方法抛出的异常

来一个例子 //OverrideTest.java package com.ylaihui.exception;import java.io.FileNotFoundException; import java.io.IOException;public class OverrideTest {public static void main(String[] args) {OverrideTest test new OverrideTest();test.display(new SubClas…

写在 2023 年初的后端社招面试经历(四年经验):字节 米哈游 富途 猿辅导

目录 背景面试准备面试 字节跳动米哈游富途牛牛猿辅导 总结 一些推荐 1. 背景 距离上一次面试[1]已经过去快 3 年了,又碰上 2022 年互联网行业大动荡,很多企业都做出了裁员决定。 身为互联网人要时刻关注自身成长,所以会对 “跟面试官切磋” …

猿人学第一题

** 猿人学系列保姆级教程 ** 第一题* 链接:http://match.yuanrenxue.com/match/1 1 分析所需参数请求来源及获取请求索要参数 上图我们可以分析出我们所需数据请求的url为http://match.yuanrenxue.com/api/match/1?me1c053a7e2130d440b11196035803350%E4%B8%A81608486311…

猿来你也在这里,北京上海无聊猿线下见面会成功举办

由Angry Rabbit暴躁兔社区、男士理容生活潮牌公司dearBOYfriend、REVO联合众多无聊猿NFT玩家举办的无聊猿聚会活动于8月7日下午在北京与上海两地同期正式举行。 本次下线活动在北京的举办地为海淀区荷清路东升园公寓Meta Space咖啡厅,参与者16人,上海举办…

猿创征文|那一年

猿创征文|那一年 那一年,因为游戏我与开发结缘那一年,我选了软件技术专业那一年,我实习了那一年,我去了一家培训外包公司那一年,我大学毕业那一年,我去了济南工作那一年,再就业那一年…

猿创征文 | Python 开发工具进化之旅

​ 前言 我为什么会接触Python? 小元是开发驱动底层的,比驱动还底层,没错就是底层设备内部程序。驱动层以下都是 C 的天下, 我为什么会接触到 Python? 2010 年的时候, 那时候 python 还没火, 那时候我就…

猿人学第1题(一天搞一个)

文章目录 前言 1. 加密方法1.1 加密寻找准备1.1.2 抓包分析1.1.3 调用堆栈前期试验理解中期操作 1.2 加密内容 2. 代码实现2.1 完整版代码2.2 python函数总结2.3 注意 前言 对js的基础一窍不通,最近才开始看。所以本文只是复现,别人的操作的逻辑。 如果…

猿人学 第一题

地址:http://match.yuanrenxue.com/match/1 思路: 1.找到传输加密地址,发现加密字段m,,通过全局搜索没有找到m值 2.通过该地址访问过的js文件排查到request,再通过断电发现m加密地方 3.找到对应的加密方…

JAVA保姆级教学(一)[安装及使用]

一.首先我们常用编写java的是idea和eclipse,在这里呢我们主要讲解的是eclipse,它的安装及配置比较复杂,小伙伴们耐心看完这篇文章,少迷路~ 二.首先,我们去官网自行下载eclipse和jdk,有的人刚接触java可能不会下载,那么…

常用服务和开放端口对照表

常用服务和开放端口对照表 代理服务器常用以下端口: (1). HTTP协议代理服务器常用端口号:80/8080/3128/8081/9080 (2). SOCKS代理协议服务器常用端口号:1080 (3). FTP&am…

常用端口号\协议\服务对照表

转载于:https://blog.51cto.com/networktech/291211

常用端口号与对应服务

1、端口号 在网络技术中,端口(Port)包括逻辑端口和物理端口两种类型。物理端口指的是物理存在的端口,如ADSL Modem、集线器、交换机、路由器上用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。逻辑端口是指逻辑意…

HTTP,TCP,UDP常见端口对照表大全

HTTP,TCP,UDP常见端口对照表,下面罗列了包括在Linux 中的服务、守护进程、和程序所使用的最常见的通信端口小贴士:CtrlF 快速查找 Http端口号(点标题可收缩或展开) No1.最常用端口 端口号码/层名称注释1tcpmuxTCP端口服务多路复用5rje远程作…

mysql实现跨服务器查询数据

在日常的开发中经常进行跨数据库进行查询数据。 同服务器下跨数据库进行查询在表前加上数据库名就可以查询到数据。 mysql跨服务器进行查询提供了FEDERATED引擎进行映射表,然后进行查询。 mysql数据库federated引擎是关闭的,首先需要先启用该引擎。my…

SQL跨表查询

目录 前言 方案一:连接多个库,同步执行查询 优点 缺点 代码执行 方案二:在主数据库增加冗余表,通过定时更新,造成同库联表查询 优点 缺点 相似实现场景 方案三:(极度不建议&#xff09…

跨库查询和多表查询

介绍如何使用MySQL进行跨库查询和多表查询(几张表的信息纵向连接在一张表中) information_schema MySQL内部使用的数据库 information_schema 和 mysql information_schema库保存了当前使用的MySQL的所有数据库,表信息,将这些信息…

Mysql不同服务器跨库查询解决方案

项目场景: Mysql在不同服务器实现跨库查询,类似dblink。 解决方案: 在两台不同服务器,实现跨库查询,其实现原理类似一个虚拟映射,需要用到mysql的另一个存储引擎Federated,FEDERATED存储引擎访问在远程数据…