老猿说说-Thread
引导语
从本章开始我们开始学习线程的知识,线程是非常有趣的一个章节,大多数同学对于线程 API,属于不用就忘,到用时需要百度的情况,希望通过本小节的源码阅读,能够加深对线程的印象。
本小节主要三章,本章主要说线程的基本概念、使用姿势、Thread 和 Runnable 的源码;Future、ExecutorService 源码解析章节主要说异步线程执行;押宝线程源码面试题章节主要说说常遇到的源码面试题。
由于线程的概念很多,所以本章会先介绍很多线程的基本概念,说清楚后再解析源码,不然有些同学会看不懂,大家见谅。
1 类注释
1.1 Thread
每个线程都有优先级,高优先级的线程可能会优先执行;
父线程创建子线程后,优先级、是否是守护线程等属性父子线程是一致的;
JVM 启动时,通常都启动 MAIN 非守护线程,以下任意一个情况发生时,线程就会停止:
退出方法被调用,并且安全机制允许这么做(比如调用 Thread.interrupt 方法);
所有非守护线程都消亡,或者从运行的方法正常返回,或者运行的方法抛出了异常;每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字。
2 线程的基本概念
我们接下来介绍一下线程的基本概念:
2.1 线程的状态
网上有各种介绍线程状态的文章,我们这里说线程的状态是从源码的角度,源码中一共列举了六种状态,如下图:
我们解析一下这个图:
- NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程的状态都是 NEW;
- 当我们运行 strat 方法,子线程被创建成功之后,子线程的状态变成 RUNNABLE,RUNNABLE 表示线程正在运行中;
- 子线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED,TERMINATED 表示线程已经运行结束了;
- 如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED,BLOCKED 表示阻塞的意思;
- 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 的作用就是让主线程等待子线程执行完成,我们画一个图示意一下:
从图中可以看出,主线程一直等待子线程沉睡 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 异常,执行结束,运行的结果如下图:
6 总结
本章主要介绍了线程的基本概念、状态、无返回值线程的初始化方式和线程的常用操作,这些知识也是工作中常用的,也是大家都必须了解的,为后面的学习打下基础。
老猿说说-Thread相关推荐
- 老猿学5G专栏完结说明
老猿学5G是因为工作原因促成的,主要目的是为了研究5G的计费架构相关内容,到今天为止,基本上达成目标,因此这个专栏基本上告一段落了. 回想这2个多月的日子,从一个对5G相关知识完全不熟悉的小白,到现在 ...
- 为什么用python写爬虫_老猿为什么写Python爬虫教程
对于"爬虫", 或许你只是听说过,或许已经有所了解.无论怎样,你可能有过这样的困惑: + 学了爬虫不知道怎么挣钱? + 技术不知道如何进阶? + 遇到问题不知道找谁交流? 十多年前 ...
- 70后.net老猿,尚能饭否?
相关阅读: 阿里P7/P8学习路线图--技术封神之路 MySQL大表优化方案,单表优化.读写分离.缓存.分区表--都在这里了 互联网技术(java框架.分布式.集群)干货视频大全,不看后悔!(免费下载 ...
- Blender基础建模 | 大帅老猿threejs特训
最近接触到了web3d相关的知识,特别感觉"大帅老猿"和"胖达老师"带我入门! 今天来和大家一起讲讲我建模的故事!顺便教大家一起实现建模自由! 一.首先下载一个 ...
- 老猿学5G扫盲贴:中国移动网络侧CHF主要功能及计费处理的主要过程
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一. ...
- 《老猿Python精品文章》专栏文章目录
☞ ░ 前往老猿Python博客 https://blog.csdn.net/LaoYuanPython ░ 一.专栏简介 <老猿Python精品文章>专栏收集的是老猿2021年开始写的一 ...
- 老猿学5G扫盲贴:N6接口用户平面协议栈对应的网络分层模型
在网络通信模型中,都对应有分层的网络结构,如开放式系统互联(OSI)的七层模型(物理层.数据链路层.网络层.传输层.会话层.表示层和应用层)以及TCP/IP四层(网络接口层.网络层.传输层和应用层)模 ...
- 老猿学5G扫盲贴:移动边缘计算(Mobile Edge Computing, MEC)
版权声明:本文为CSDN博主「魏晓蕾」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/gongxifaca ...
- 老猿学5G扫盲贴:与用户和终端相关的名词UE、SUPI、GPSI、PEI
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 和4 ...
最新文章
- 双边滤波算法的原理、流程、实现及效果
- 正则判断 手机邮箱的正确格式
- android水平滚动条,Android使用HorizontalScrollView实现水平滚动
- Oracle 实例崩溃恢复原理剖析 -- 检查点队列的作用与意义
- login.html id=9453,SN100C Sublizenzierung und Aufnahme in die ISO 9453
- SQLSTATE[HY000]: General error: 1030 Got error 28 from storage engine
- 知乎App有哪些非常“贴心”的UI设计
- Android 自定义View(二)绘制一个封闭多边形
- mingw,cygwin,gnuwin32,msys,msys2 的区别
- MySQL查询优化和索引优化学习笔记
- 富士胶片滤镜人像调色预设PS插件
- 操作系统课程设计 —— 模拟磁盘文件系统实现 (Java)
- Cobalt Strike之网站克隆
- 方根法公式_Excel怎么对一个数开n次方根 Excel开方方法汇总
- 解决“微信与此IPAD不兼容
- Python 爬取微信公众号文章和评论 (有源码)
- 作者已死?AI正用艺术征服人类
- pands 画图 调整大小_关于cad画图比例,图纸比例,打印比例问题详解
- EASEUS Partition Master 调整Windows 7分区
- 怎么检查计算机启动程序,如何查看电脑开机启动项
热门文章
- 如何避免期货程序化交易中的过度拟合现象
- MATLAB Simulink中Scope数据导入到工作区
- QQ语录(随时更新)
- 工厂设计模式(java版本、spring源码中使用的工厂模式)
- openmp matlab,openmp和mex问题
- 【Bug】No validator... javax.validation.constraints.NotBlank validating type java.lang.Integer
- 东华研究生复试------22 骑士斗恶龙
- jquery给p标签赋值
- 初识Android framework层
- win10 开机慢优化方法