详细分析 Java 中启动线程的正确和错误方式
启动线程的正确和错误方式
前文回顾
start 方法和 run 方法的比较
代码演示:
/**
* <p>
* start() 和 run() 的比较
* </p>
*
* @author 踏雪彡寻梅
* @version 1.0
* @date 2020/9/20 - 16:15
* @since JDK1.8
*/
public class StartAndRunMethod {
public static void main(String[] args) {
// run 方法演示
// 输出: name: main
// 说明由主线程去执行的, 不符合新建一个线程的本意
Runnable runnable = () -> {
System.out.println("name: " + Thread.currentThread().getName());
};
runnable.run();
// start 方法演示
// 输出: name: Thread-0
// 说明新建了一个线程, 符合本意
new Thread(runnable).start();
}
}
从以上示例可以分析出以下两点:
直接使用
run方法不会启动一个新线程。(错误方式)start方法会启动一个新线程。(正确方式)
start 方法分析
start 方法的含义以及注意事项
start方法可以启动一个新线程。线程对象在初始化之后调用了
start方法之后, 当前线程(通常是主线程)会请求 JVM 虚拟机如果有空闲的话来启动一下这边的这个新线程。也就是说, 启动一个新线程的本质就是请求 JVM 来运行这个线程。
至于这个线程何时能够运行,并不是简单的由我们能够决定的,而是由线程调度器去决定的。
如果它很忙,即使我们运行了
start方法,也不一定能够立刻的启动线程。所以说
srtart方法调用之后,并不意味这个方法已经开始运行了。它可能稍后才会运行,也很有可能很长时间都不会运行,比如说遇到了饥饿的情况。这也就印证了有些情况下,线程 1 先掉用了
start方法,而线程 2 后调用了start方法,却发现线程 2 先执行线程 1 后执行的情况。总结: 调用
start方法的顺序并不能决定真正线程执行的顺序。注意事项
start方法会牵扯到两个线程。第一个就是主线程,因为我们必须要有一个主线程或者是其他的线程(哪怕不是主线程)来执行这个
start方法,第二个才是新的线程。很多情况下会忽略掉为我们创建线程的这个主线程,不要误以为调用了
start就已经是子线程去执行了,这个语句其实是主线程或者说是父线程来执行的,被执行之后才去创建新线程。
start方法创建新线程的准备工作首先,它会让自己处于就绪状态。
- 就绪状态指已经获取到除了 CPU 以外的其他资源, 如已经设置了上下文、栈、线程状态以及 PC(PC 是一个寄存器,PC 指向程序运行的位置) 等。
做完这些准备工作之后,就万事俱备只欠东风了,东风就是 CPU 资源。
做完准备工作之后,线程才能被 JVM 或操作系统进一步去调度到执行状态等待获取 CPU 资源,然后才会真正地进入到运行状态执行
run方法中的代码。
需要注意: 不能重复的执行 start 方法
代码示例
/**
* <p>
* 演示不能重复的执行 start 方法(两次及以上), 否则会报错
* </p>
*
* @author 踏雪彡寻梅
* @version 1.0
* @date 2020/9/20 - 16:47
* @since JDK1.8
*/
public class CantStartTwice {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("name: " + Thread.currentThread().getName());
};
Thread thread = new Thread(runnable);
// 输出: name: Thread-0
thread.start();
// 输出: 抛出 java.lang.IllegalThreadStateException
// 即非法线程状态异常(线程状态不符合规定)
thread.start();
}
}
报错的原因
start一旦开始执行,线程状态就从最开始的 New 状态进入到后续的状态,比如说 Runnable,然后一旦线程执行完毕,线程就会变成终止状态,而终止状态永远不可能再返回回去,所以会抛出以上异常,也就是说不能回到初始状态了。这里描述的还不够清晰,让我们来看看源码能了解的更透彻。
start 方法源码分析
源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
// 第一步, 检查线程状态是否为初始状态, 这里也就是上面抛出异常的原因
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
// 第二步, 加入线程组
group.add(this);
boolean started = false;
try {
// 第三步, 调用 start0 方法
start0();
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 */
}
}
}
源码中的流程
第一步:
启动新线程时会首先检查线程状态是否为初始状态, 这也是以上抛出异常的原因。即以下代码:
if (threadStatus != 0)
throw new IllegalThreadStateException();
其中 threadStatus 这个变量的注释如下,也就是说 Java 的线程状态最初始(还没有启动)的时候表示为 0:
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
第二步:
将其加入线程组。即以下代码:
group.add(this);
第三步:
最后调用 start0() 这个 native 方法(native 代表它的代码不是由 Java 实现的,而是由 C/C++ 实现的,具体实现可以在 JDK 里面看到,了解即可), 即以下代码:
boolean started = false;
try {
// 第三步, 调用 start0 方法
start0();
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 */
}
}
run 方法分析
run 方法源码分析
@Override
public void run() {
// 传入了 target 对象(即 Runnable 接口的实现), 执行传入的 target 对象的 run 方法
if (target != null) {
target.run();
}
}
对于 run 方法的两种情况
第一种: 重写了
Thread类的run方法,Thread的run方法会失效, 将会执行重写的run方法。第二种: 传入了
target对象(即Runnable接口的实现),执行Thread的原有run方法然后接着执行target对象的run方法。总结:
run方法就是一个普通的方法, 上文中直接去执行run方法也就是相当于我们执行自己写的普通方法一样,所以它的执行线程就是我们的主线程。所以要想真正的启动线程,不能直接调用
run方法,而是要调用start方法,其中可以间接的调用run方法。
如有写的不足的,请见谅,请大家多多指教。
详细分析 Java 中启动线程的正确和错误方式的更多相关文章
- 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)
详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...
- java 中创建线程有哪几种方式?
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...
- 注解式项目开发!详细解析Java中各个注解的作用和使用方式
@Target 作用: 指明了修饰的这个注解的使用范围, 即被描述的注解可以用在哪里 @Target(ElementType.Type) ElementType取值的类型: TYPE: 类,接口或者枚 ...
- 【Java中的线程】java.lang.Thread 类分析
进程和线程 联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服.这样开水烧完,衣服洗的也差不多了.这 ...
- 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!
碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...
- Java中的线程
http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...
- [转]详细介绍java中的数据结构
详细介绍java中的数据结构 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类.一起来看本文吧! 也许你已经熟练使用了java.util包里面的各 ...
- 详细介绍java中的数据结构
详细介绍java中的数据结构 http://developer.51cto.com/art/201107/273003.htm 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个 ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
随机推荐
- java基本数据类型总结 类型转换 final关键字的用法
java基本数据类型总结 Java数据类型总结 数据类型在计算机语言里面,是对内存位置的一个抽象表达方式,可以理解为针对内存的一种抽象的表达方式.接触每种语言的时候,都会存在数据类型的认识,有复杂的. ...
- 修改docker0网桥的IP段
关闭docker进程 systemctl stop docker 修改/etc/docker/daemon.json { "bip": "100.96.2.1/24&qu ...
- 面经手册 · 第8篇《LinkedList插入速度比ArrayList快?你确定吗?》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你以为考你个数据结构是要造火箭? 汽车75马力就够奔跑了,那你怎么还想要2.0涡轮+ ...
- 树(Tree)解题总结
定义 树是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合.它是由 n(n>0) 个有限节点组成一个具有层次关系的集合.. 二叉搜索树(Binar ...
- Python 编程开发 实用经验和技巧
文章目录 一.小数保留指定位小数 1.%f 方法 2.format函数 3.round()函数 4.直接截断 二.判断变量的数据类型的两种方法 1.type(): 2.isinstance() 三.p ...
- Promise、Generator,Async/await
我们知道JavaScript是单线程语言,如果没有异步编程非得卡死. 以前,异步编程的方法有下面四种 回调函数 事件监听 发布/订阅 Promise对象 现在据说异步编程终极解决方案是——async/ ...
- [源码解析] Flink的Slot究竟是什么?(2)
[源码解析] Flink 的slot究竟是什么?(2) 目录 [源码解析] Flink 的slot究竟是什么?(2) 0x00 摘要 0x01 前文回顾 0x02 注册/更新Slot 2.1 Task ...
- B - The Staircases (dp)
One curious child has a set of N little bricks. From these bricks he builds different staircases. St ...
- 面试:为了进阿里,又把并发CAS(Compare and Swap)实现重新精读一遍
该系列文章已收录在公众号[Ccww技术博客],原创技术文章第一时间推出 前言 在面试中,并发线程安全提问必然是不会缺少的,那基础的CAS原理也必须了解,这样在面试中才能加分,那来看看面试可能会问那些问 ...
- 09vuex
state 大白话:获取state的值 vuex中state数据是响应式的.只要state中数据发生变化.vue组件自动更新. 要想做到响应式 前提:是在mutaion中定义函数修改state值. 最 ...