初始JavaEE篇——多线程(1):Thread类的介绍与使用
找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
目录
创建线程
1、继承 Thread类
2、实现Runnable接口
3、使用匿名内部类
1)继承Thread类的匿名内部类
2)实现Runnable接口
查看线程的状态
Thread类的常见属性与方法
创建线程
Thread 是线程的意思,在Java中也是通过 Thread类中的 start 方法启动一个线程。但要注意的是,每一个线程对象都只能调用一次 start方法,如果后续再去调用的话,便会抛异常。
在Java中有四种方式实现多线程。
1、继承 Thread类
语法格式:
// 继承Thread类
class MyThread extends Thread {@Overridepublic void run() {....... // 代码}
}public class Test {public static void main(String[] args) {Thread t = new MyThread();// 启动 t线程,调用run方法t.start();// 后续代码.......}
}
我们创建一个新的类,使其继承Thread类,并重写其中的run方法。这就已经具备了一个线程的基本雏形,现在只要我们在main线程中去实例化这个类,并且启动这个线程即可。启动线程的方法是通过调用线程对象的start方法,start方法会启动一个新的线程并在这个新线程中自动调用run方法,完成该线程的所执行的任务。
// 1、继承Thread类
class MyThread extends Thread {@Overridepublic void run() { // 重写run方法while (true) {System.out.println("Hello Thread");}}
}public class Test {public static void main(String[] args) {Thread t = new MyThread();// 启动 t线程,调用run方法t.start(); // 这里发生了多态while (true) {System.out.println("Hello main");}}
}
上述代码运行后,会出现 Hello main 和 Hello Thread 循环交替的打印,这便是多线程。main线程在运行的同时,t 线程也在运行。

从结果上也能看出这是在并发执行,main线程在CPU上运行一段时间,t 线程接着在CPU上运行一段时间。 我们可以使用 sleep 方法来放慢观察。与C语言中的sleep函数一样,是让程序休眠的方法。下面就来详细介绍一下:
Java中的sleep方法是Thread类的一个静态方法,参数是要休眠时间,单位是毫秒。我们可以先写一个代码来演示:
public class Test {public static void main(String[] args) throws InterruptedException {while (true) {System.out.println("Hello main");Thread.sleep(1000); // 会抛异常,需要throws或者try-catch}}
}
上面的代码会打印一个 Hello main 之后,休眠1000毫秒,即1秒。
注意:这个休眠是让线程主动的放弃CPU资源。
现在我们可以近距离观察多线程的运行情况了。
// 1、继承Thread类
class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();// 启动 t线程,调用run方法t.start(); // 这里发生了多态while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}
运行结果:

有细心的小伙伴应该发现了,使用sleep方法时, run方法和main方法中的处理方式不一样,run方法是通过try-catch的形式,而main方法中两种都是可以的。这是因为run方法是重写了Thread类中的run方法,父类的没有throws这个操作,因此子类在重写时也不能有throws操作,不然就会改变run方法的框架,从而不符合重写的概念。
2、实现Runnable接口
// 实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {....... // 代码}
}public class Test {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());// 启动 t线程,并调用run方法t.start();// 后续代码}
}
我们创建一个新的类,实现 Runnable接口,重写 run方法。接着在main方法中将实例化一个Thread类对象,将 实现Runnable接口的类实例化作为参数传入Thread中。
代码演示:
class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new MyRunnable());// 启动 t线程,调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}
运行结果:

注意:我们使用t.run方法也能执行代码,但是得不到 t.start的效果。因为t.run方法不会创建线程,只是一个简单的方法调用,那么最终代码就会一直死循环打印 Hello Thread。如下所示:

使用 t.start 时,是先创建的线程,然后再去调用的run方法,因此 t 线程和main线程会并发执行。
上面是两种主要的创建线程的方式,剩下的两种就是通过对上面的变形来编写的。
3、使用匿名内部类
使用匿名内部类有两种选择,一个是继承Thread类,一个是实现Runnable接口
1)继承Thread类的匿名内部类
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(){@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};// 启动 t线程,调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

当然除了上面这种写法,还有另一种写法:
public class Test {public static void main(String[] args) throws InterruptedException {new Thread(){@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start(); // 这里启动了线程,并调用了run方法while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}
第一种写法是通过父类引用来接收子类对象,而第二种写法就是一次性的线程,当这次使用完成之后,后续再也不能使用该线程了。
2)实现Runnable接口
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(// 创建一个匿名对象,该对象是实现了Runnable接口的匿名内部类new Runnable() {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});// 启动 t线程,并调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}
由于 Runnable 接口是函数式接口(一个接口中只包含一个抽象方法),因此我们可以使用lambda表达式来简写。
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 启动 t线程,并调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}
lambda表达式:"()" 这个要重写的方法的参数 ,"->" 是必不可少的,"{}" 这里面是要重写的方法的方法体,即具体的代码。
以上就是创建线程的全部方法了。
我们在日常的开发中,使用最多的就是通过实现 Runnable 接口来创建线程。因为我们在使用 Runnable 接口创建线程时,是将实现该接口的对象作为参数传入,这样后续如果要修改代码的话,也只需要在实现该接口的内部进行修改,而不需要再去改动main线程中的代码了。这样代码之间的依赖程度就降低了,而是通过模块来之间来依赖维护,也就达到了高内聚、低耦合的效果。而使用 lambda 表达式来简写代码的方式则是既方便,可读性也不差。因此我们基本上是使用 lambda 表达式来简写实现 Runnable 接口来创建线程。
查看线程的状态
我们可以通过JDK自带的工具来查看当前进程的状态以及具体线程的相关状态。
1、找到安装JDK的目录。
2、找到bin目录下的 jconsole 这个.exe文件,并双击运行。
3、找到要连接的进程,一般对应的类名,然后点击连接即可。下图是双击运行后的界面:




以上就是关于如果查看线程相相关信息的操作。
Thread类的常见属性与方法
上面我们是采用了Thread类的两种构造方法来创建线程的,其实除了上面的方法外还有其他的几种构造方法。
| 方法 | 描述 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
| Thread(ThreadGroup group, Runnable target) | 这个暂时不需要用到,后期再学习 |
注意:使用 Thread() 和 Thrad(String name),这两个方法都是要重写 run 方法的,而另外两个虽然使用 Runnable,但本质上还是要重写 run 方法。因此对于一个线程最重要的就是 run方法,虽然 start 方法是启动线程,但是只要 run 方法执行完毕,通常这个线程就会被销毁。
下面就来学习线程的常见属性:
| 属性 | 获取方法 |
| ID | getld() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程(守护线程) | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | islnterrupted() |
解释:
1、ID 是线程的唯一标识、名称是各种调试工具用到、状态表示线程当前所处的一个情况:就绪态、阻塞态、运行态等。
2、优先级是指当多个线程同时被调度时,由于是随机调度的,不能确保某个线程一定会被第一时间执行,但如果某个线程的优先级比较高的话,那么就会先运行该线程。
3、后台线程也叫作守护线程,即这个线程只是默默守护, 并不会干扰到程序的正常运行,当程序正常运行结束后(进程结束了),后台线程也就被销毁了,但是前台线程不一样,只有当全部的前台线程运行结束后,进程才会结束。简单理解就是,后台线程不能干预进程的结束,而前台线程可以干预进程的结束。
从上面,我们查看线程的状态那里可以看出来,我们自己创建的线程默认都是前台线程。我们也可以通过代码来观察:
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 设置成后台线程t.setDaemon(true);// 启动 t线程,并调用run方法t.start();for (int i = 0; i < 3; i++) {System.out.println("Hello main");Thread.sleep(1000);}System.out.println("main线程结束!进程也就结束了");}
}
运行结果:

注意:设置线程的属性一定要在调用start方法之前,因为一旦调用start方法之后,线程启动了,那么就是按照其默认的属性来进行运行了,则最终不会停下来。
4、线程是否存活,简单理解就是看run方法是否执行完毕,但有些特殊情况下,即使run方法执行完毕,线程依旧存活。
我们也是可以通过代码来观察的。
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{System.out.println("Hello Thread");System.out.println("t线程结束");});// 看看线程启动前是否存活System.out.println(t.isAlive());t.setDaemon(true);// 启动 t线程,并调用run方法t.start();// 看看结束前是否存活System.out.println(t.isAlive());// 确保 t线程已经结束了Thread.sleep(1000);// 看看结束后是否存活System.out.println(t.isAlive());System.out.println("main线程结束,进程结束!");}
}
运行结果:

从运行的结果,我们可以看出启动前、结束后线程都已经灭亡了,当线程还在执行run方法时,线程就是存活的。
5、线程的中断就是让线程结束,而让线程结束就只有一个办法:让 run方法提前结束,而让run方法提前就是让while循环提前结束,即在while循环的条件语句中来判断。
我们可以用代码来演示:
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()) { // 判断是否是中断System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t线程结束");});// 启动 t线程,并调用run方法t.start();Thread.sleep(3000);// 中断t.interrupt();System.out.println("main线程结束");}
}
运行结果:

首先,来解读代码:
1、因为 t 在lambda表达式之后才才会创建,因此我们在调用 isInterrupted 方法时,不能通过 t 来调用,要通过 Thread.currentThread 方法来获取当前的类对应的对象,然后再用这个对象去调用 isInterrupted 方法。
2、Thread.currentThread().isInterrupted() 方法会返回 false ,因为线程在执行的过程中是处于非中断的状态,因此我们要对其进行 取反操作,才能使其进入while循环。
3、 当我们编写 t.interrupt() 的代码时,线程确实会中断,但是由于 打印语句的执行是非常快的,因此在 t 线程的生命周期中,绝大部分时间是处于休眠状态,而此方法会唤醒sleep,使其抛出异常,因此只要我们在 throw 异常的代码中,使用break 或者 啥也不做,那么就不会抛出异常。

上述是使用 break的效果,但如果我们 啥也不干的话,发现不会停下来。

其实是 sleep 在捣乱。正常来说,调用 Interrupt 方法将 isInterrupted 方法内部的标志位设置为了 true ,但由于上述代码中把 sleep 给唤醒了,这种提前唤醒的情况下,sleep 就会在唤醒之后,把 islnterrupted 标志位给设置回 false。所以后续在进行判断的时候,还是会进入while循环。
好啦!本期 初始JavaEE篇——多线程(1):Thread类的介绍与使用 的学习之旅到此结束啦!我们下一期再一起学习吧!
相关文章:
初始JavaEE篇——多线程(1):Thread类的介绍与使用
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程(ಥ_ಥ)-CSDN博客 所属专栏:JavaEE 目录 创建线程 1、继承 Thread类 2、实现Runnable接口 3、使用匿名内部类 1)继承Thread类的匿名内部类 2)…...
基于单片机的LED照明自动控制系统的设计
本设计主控核心芯片选用了AT89C51单片机,接入了光照采集模块、红外感应模块、继电器控制模块,通过控制发光二极管模拟教室智能灯组的控制。首先通过光敏感应的方式感应当前光照环境为白天还是夜晚,同时,红外感应模块感应是否有人。…...
C语言——头文件的使用
目录 前言头文件怎么包含 前言 这个专栏会专门讲一些C语言的知识,后续会慢慢更新,欢迎关注 C语言专栏 头文件怎么包含 在使用头文件的过程中,我们经常会遇到重定义、重复包含等问题,那么怎么编写头文件和使用头文件才能解决这些…...
LeetCode 精选 75 回顾
目录 一、数组 / 字符串 1.交替合并字符串 (简单) 2.字符串的最大公因子 (简单) 3.拥有最多糖果的孩子(简单) 4.种花问题(简单) 5.反转字符串中的元音字母(简单&a…...
【Unity - 屏幕截图】技术要点
在Unity中想要实现全屏截图或者截取某个对象区域的图片都是可以通过下面的函数进行截取 Texture2D/// <summary>/// <para>Reads the pixels from the current render target (the screen, or a RenderTexture), and writes them to the texture.</para>/…...
句句深刻,字字经典,创客匠人老蒋金句出炉,哪一句让你醍醐灌顶?
注意力经济时代、流量经济时代、短视频经济时代,创始人到底应该如何做,才能抓住风口,链接未来? 「创始人IP创新增长班」线下大课现场,老蒋作为主讲导师,再一次用他丰富的行业经验与深刻的时代洞察ÿ…...
柯尼卡美能达CA-310 FPD色彩分析仪
柯尼卡美能达CA-310 FPD色彩分析仪 型 号:CA-310 名 称:FPD色彩分析仪 品 牌:柯尼卡美能达(KONICA MINOLTA) 分 类:光学和色彩测试 > 光学、显示与色彩测量 > 色彩分析仪 产品属性:主机 简 述&…...
二维EKF的MATLAB代码
EKF二维滤波 MATLAB 实现 提升您的数据处理能力!本MATLAB程序实现了扩展卡尔曼滤波(EKF)在二维状态估计中的应用,专为需要高精度定位和动态系统分析的用户设计。通过精确的滤波技术,有效减少噪声影响,确保…...
大数据治理:数据时代的挑战与应对
目录 大数据治理:数据时代的挑战与应对 一、大数据治理的概念与内涵 二、大数据治理的重要性 1. 提高数据质量与可用性 2. 确保数据安全与合规 3. 支持数据驱动的决策 4. 提高业务效率与竞争力 三、大数据治理的实施策略 1. 建立健全的数据治理框架 2. 数…...
绿联NAS免驱安装MacOS
前段时间UGOS Pro迎来了一次大更新,Docker新增了Docker Compose堆栈项目,于是便在Docker Hub找了个支持Docker Compose部署的MacOS开源项目来验证一下,顺便体验一下用N100运行是什么感觉。 开始折腾 先说说,在没用Docker Compos…...
聊聊ASSERT处理在某些场景下的合理用法
先看看ASSERT的介绍: 编写代码时,我们总是会做出一些假设,ASSERT断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真…...
SAP Odata 服务
参考过程 SAP创建ODATA服务-Structure_sap odata-CSDN博客 案例...
【java数据结构】栈
【java数据结构】栈 一、栈的概念二、 栈的使用三、 栈的模拟实现(数组)构造方法size()empty()push()pop()peek() 四、 栈的模拟实现(链表)构造方法size()empty()push()pop()peek() 五、 栈的例题 此篇博客希望对你有所帮助(帮助你了解栈),不…...
从头开始的可视化数据 matplotlib:初学者努力绘制数据图
从头开始学习使用 matplotlib 可视化数据,对于初学者来说,可能会有些挑战,但 matplotlib 的核心理念非常清晰:绘制图表需要了解如何设置图形、坐标轴以及如何用数据填充它们。我们可以通过一些简单的例子来逐步介绍基本步骤。 1. …...
vscode 远程linux服务器 连接git
vscode 远程linux服务器 连接git 1. git 下载2. git 配置1)github 设置2)与github建立连接linux端:创建密钥github端:创建ssh key 3. 使用1)初始化repository2)commit 输入本次提交信息,提交到本…...
不同jdk版本中的接口规范
Java Development Kit(JDK)的每个版本通常会对 Java 语言和类库进行改进,接口规范也在不断演进。Java 接口的演变是逐步从 “纯粹抽象的定义” 向 “具有行为的抽象定义” 演化的。 JDK 1.0 和 JDK 1.1JDK 1.2 到 JDK 1.6JDK 1.8(…...
人工智能图像信号处理器(AI ISP)技术介绍
随着智能设备和数码成像技术的快速发展,图像质量的提升成为用户体验的关键因素之一。人工智能图像信号处理器(AI Image Signal Processor,AI ISP) 作为传统图像信号处理器(ISP)的升级版,通过集成…...
3D Slicer 教程三 ---- 坐标系
上篇提到3D Slicer 教程二 ---- 数据集-CSDN博客 3d slicer的坐标系与大多数医学影像软件使用LPS(左、后、上)坐标系统不太一样, 今天就仔细介绍一下坐标系的区别,复盘一下在影像处理中遇到的坐标问题(集中在坐标处理相关的,图像插值,图像处理, 定位线,翻…...
Video-LLaMA论文解读和项目部署教程
Video-LLaMA: An Instruction-tuned Audio-Visual Language Model for Video Understanding 相关工作 大型语言模型: 本文的工作基于这些LLM,并提供即插即用插件,使其能够理解视频中的视觉和听觉内容。 多模态大型语言模型: 现有…...
Elasticsearch设置 X-Pack认证,设置账号和密码
前言 以下Elasticsearch版本:7.9.3 ES自带的X-Pack密码验证: X-Pack是elasticsearch的一个扩展包,将安全,警告,监视,图形和报告功能捆绑在一个易于安装的软件包中,所以我们想要开启账号密码验证…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式
pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图,如果边框加在dom上面,pdf-lib导出svg的时候并不会导出边框,所以只能在echarts图上面加边框 grid的边框是在图里…...
STL 2迭代器
文章目录 1.迭代器2.输入迭代器3.输出迭代器1.插入迭代器 4.前向迭代器5.双向迭代器6.随机访问迭代器7.不同容器返回的迭代器类型1.输入 / 输出迭代器2.前向迭代器3.双向迭代器4.随机访问迭代器5.特殊迭代器适配器6.为什么 unordered_set 只提供前向迭代器? 1.迭代器…...
