Java 多线程系列Ⅰ(创建线程+查看线程+Thread方法+线程状态)
多线程基础
- 一、创建线程的五种方法
- 前置知识
- 1、方法一:使用继承Thread类,重写run方法
- 2、方法二:实现Runnable接口,重写run方法
- 3、方法三:继承Thread,使用匿名内部类
- 4、方法四:实现Runnable,使用匿名内部类
- 5、方法五:使用lambda表达式(常用)
- 二、体验多线程
- 查看线程详情
- 三、Thread及常见方法
- 1、构造方法
- 2、线程属性获取方法
- 3、启动线程-start()
- 4、中断一个线程:(让一个线程停下来)
- 5、等待一个线程-join()
- 四、线程的状态
一、创建线程的五种方法
前置知识
Thread
类是用于创建和操作线程的类。每个线程都必须通过 Thread 类的构造方法创建,并实现run()
方法来执行线程的任务。run()
方法是 Thread 类中用于定义线程要执行的任务的方法。当一个线程被启动后,它会调用自己的 run() 方法,在该方法中执行线程的任务逻辑。- 需要注意的是,直接调用 run() 方法并不会启动一个新的线程,而只会在当前线程中依次执行 run() 方法中的代码。如果要启动一个新的线程并执行 run() 方法中的代码,应该使用
start()
方法来启动线程。
1、方法一:使用继承Thread类,重写run方法
class MyThread extends Thread {//run是线程的入口方法@Overridepublic void run() {System.out.println("Hello t");}
}public class ThreadDemo1 {//这种方式是使用Thread 的run来描述线程入口public static void main(String[] args) throws InterruptedException {// Thread通过接收重写Thread内部run方法的子类Thread t = new MyThread();// start 启动线程t.start();}
}
2、方法二:实现Runnable接口,重写run方法
在Java中,Runnable是一个函数式接口(Functional Interface),用于表示要在一个线程中执行的任务。
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("hello t");}
}public class ThreadDemo2 {public static void main(String[] args) throws InterruptedException {// 1.先实例化实现了Runnable接口的类MyRunnable runnable = new MyRunnable();// 2.通过Thread的构造方法,传入runnable任务,创建线程Thread t = new Thread(runnable);t.start();}
}
3、方法三:继承Thread,使用匿名内部类
public class ThreadDemo3 {public static void main(String[] args) {// 此处的new Thread(){...};就相当于一个继承了Thread类的子类Thread t = new Thread(){@Overridepublic void run() {System.out.println("hello t");}};t.start();}
}
4、方法四:实现Runnable,使用匿名内部类
public class ThreadDemo4 {public static void main(String[] args) {// 此处的new Runnable(){...}就相当于一个实现了Runnable接口的类Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello t");}});t.start();}
}
5、方法五:使用lambda表达式(常用)
虽然上面四种方式都可以达到创建线程的目的,但都不是常用的写法,推荐使用 lambda
表达式是最简单最直观的写法!
回顾lambda表达式
提到 lambda 表达式,下面我们在来回顾一下:
lambda
表达式,本质上就是一个匿名函数。(Java里面,函数(方法)是无法脱离类的,在Java里面 lambda 就相当于是一个例外,它可以将一个函数(或者说方法)作为参数传递到另一个方法中,而不需要将它包含在一个类中。)
虽然说,lambda
表达式可以在⼀定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简洁实现的。lambda 表达式毕竟只是⼀个匿名方法。当实现的接口中的方法过多或者多少的时候,lambda表达式都是不适用的。lambda 表达式,只能实现函数式接口。
函数式接口:如果说,⼀个接口中,要求实现类必须实现的抽象方法,有且只有⼀个,这样的接口,就是函数式接口。
语法规则:
interface Demo {public void test();
}
public class Test {public static void main(String[] args) {// 使用lambda表达式实现接口Demo demo = () -> {System.out.println("test");};demo.test();}
}
其他规定:
()
里面放参数,如果只有一个参数,可以省略 (){}
里面放函数体,如果只有一行代码,也可以省略 {}- “变量捕获”,lambda 表达式要想访问外面的局部变量,java 要求变量必须是 final 或是 “等效final”(即变量中没有用final修饰,但是代码中并没有做出修改)
使用 lambda 创建线程
public class ThreadDemo5 {public static void main(String[] args) throws InterruptedException {// lambda本质上是实现了Runnable接口Thread t = new Thread(()->{System.out.println("hello t");});t.start();}
}
二、体验多线程
运行以下代码体验以下多线程情况下的代码执行。
下面代码中用到了sleep 方法,关于sleep
方法说明:
在 Java 中,Thread 类中的
sleep(long millis)
方法用于使当前线程进入休眠状态,暂停执行一段时间。该方法接受一个以毫秒为单位的时间参数,表示要休眠的时间长度。
当线程处于休眠状态时,它不会占用CPU资源,也不会执行任何代码,直到休眠时间结束。在休眠期间,线程可以被中断(通过调用interrupt()
方法),或者其他线程可以抢占 CPU 资源,使得该线程处于等待状态。
public class ThreadDemo5 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello t");}},"t");t.start();while (true) {Thread.sleep(1000);System.out.println("hello main");}}
}
- 上述代码涉及两个线程:(1)
main
方法所对应的线程(一个进程中至少有一个线程)也可称为主线程。(2)t
线程- 运行程序,其实就是idea对应的进程创建了一个新的 java进程,这个java进程用来执行自己写的代码。同时这个 java进程里有两个线程,一个是main,一个是t,每个线程都是一个独立的执行流。此时的 hello t 是由 t 线程执行的打印逻辑。
- 这里的交替打印并不是严格意义上的交替,每一秒过后,先打印main还是先打印 t 是不确定的,因为多个线程在 CPU 上调度执行的顺序是不确定的(随机的)。
查看线程详情
我们可以使用 jdk 提供的第三方工具,查看java进程里面的线程详情:
注意事项:
jconsole
只能分析 Java 进程。- 运行就 jconsole,如果进程列表为空,可以尝试以管理员身份运行。
点进当前代码的进程对应的线程:
三、Thread及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,
每个线程都有一个唯一的 Thread 对象与之关联,即Java代码中的Thread对象和操作系统中的线程是一一对应的
。而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
1、构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
注意:name 名字参数,是给线程起了个名字,这里的名字不影响程序的执行,只是方便我们在调试的时候,快速找到需要的线程。
2、线程属性获取方法
返回值类型 | 方法名 | 说明 |
---|---|---|
long | getId() | 返回线程标识符Id |
String | getName() | 返回线程名称 |
Thread.State | getState() | 返回线程状态 |
int | getPriority() | 返回线程优先级 |
boolean | isDaemon() | 判断是否为后台线程 |
boolean | isAlive() | 判断线程是否存活 |
boolean | isInterrupted() | 判断线程是否被中断 |
(1)isDaemon()-前台线程后台线程说明:
- isDaemon()返回true-表示后台线程,后台线程不阻止Java进程结束,哪怕后台线程还没执行完,Java进程该结束就结束。
- isDaemon()返回false-表示前台线程,前台线程会阻止Java进程结束,必须得Java进程中所有的前台线程执行完Java进程才能结束。
注:创建的线程默认是前台的,可以通过setDaemon(true)设置成后台的。
(2)isAlive()线程存活说明
描述的是系统内核里哪个线程是否存活,也就是说只有调用start()方法(调用 start 方法,
才真的在操作系统的底层创建出一个线程),启动线程之后,当线程正在执行时返回true,否则返回false
(3)isInterrupted()后面详细介绍…
3、启动线程-start()
上面介绍的五种创建线程的方式,都是通过覆写 run()
方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。只有调用了start()
方法,才真正从系统这里创建一个线程。
调用 start 方法, 才真的在操作系统的底层创建出一个线程。
4、中断一个线程:(让一个线程停下来)
中断一个线程,就是让一个线程停下来,即线程终止,本质上来说,线程终止就是让该线程的入口方法执行完毕。这里的执行完毕可以是 return 返回、代码执行完毕、抛出异常 等情况。具体来说,可以采取如下策略:
(1)给线程设置一个结束标志位
例如:设置标志位isQuite
作为线程结束的标志
public class ThreadExample_Interrupted {// 由于"变量捕获",这里将isQuite设置为成员变量public static boolean isQuite = false;public static void main(String[] args) {Thread t = new Thread(()->{while (!isQuite) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t线程终止");});t.start();// 3秒后,在主线程中修改isQuitetry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}isQuite = true;}
}
注意:这种情况下是将isQuite设置成了
成员变量
,如果将其设置成局部变量,正常情况下由于线程和线程之间共用一个内存地址空间,语法上是成立的,但是对于lambda表达式要是想要访问外面的局部变量
,这时就涉及到了Java变量捕获,即捕获的变量必须是 final 或者 “等效final”,即变量中没有用final修饰,但是代码中并没有做出修改。
(2)使用Thread类内置的标志位
方法名称 | 说明 |
---|---|
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
public void interrupt() | 中断对象关联的线程。如果线程正在阻塞,则以异常方式通知,否则设置标志位true。 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位。 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位。 |
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记。根据上述提供的方法,我们可以使用 Thread.interrupted()
或者 Thread.currentThread().isInterrupted()
代替自定义标志位。
例如:还是上面的例子,这次我们使用内置标志位
public class ThreadExample_Interrupted2 {public static void main(String[] args) {Thread t = new Thread(()->{// 此处 currentThread currentThread 是获取到当前线程实例 twhile (!Thread.currentThread().isInterrupted()) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 将内部的标志位设置成truet.interrupt();}
}
注意:通过上面结果,我们看到,即使sleep被强制唤醒后,触发了两件事:
- 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException异常的形式通知.
- 清除中断标志.
因此我们会看到,抛出异常后,t 线程中的循环继续进行(因为此时sleep被唤醒后清空了标志位 true->false)
对于 interrupt
只是通知不是命令,至于为什么 Java 不强制设置成“命令结束”的操作,主要是因为,这种强制性的设定是非常不友好的,对于线程 线程何时结束,始终是线程本身最清楚,所以还是交给线程自身来决定比较好。
5、等待一个线程-join()
线程之间是并发执行的,操作系统对于线程的调度是无序的,无法判断两个线程谁先执行结束,谁后执行结束。然而有时候有需要明确规定线程的结束顺序,这时就可以使用线程等待-join来实现。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒。 |
例如:我们需要等待一个线程t完成它打印工作后,才能进行main线程的打印工作。
public class ThreadExample_join {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello t");}});t.start();// 正常情况下,如果不加join,大部分情况下是先打印hello main(因为创建线程也是需要开销的)t.join();//这里就使t线程先执行完,main暂时阻塞System.out.println("hello main");}
}
上述代码在main线程中调用 t.join:如果 t 线程还没结束,main 线程就会“阻塞”等待-Blocking。也就是说代码执行到 t.join 时就停下来了,当前这个线程暂时不参与CPU的调度执行了。直到 t 线程执行完毕,此时 main 解除阻塞后将继续向下执行。
四、线程的状态
操作系统中的线程,自身是有一个状态的,但是Java 中Thread是对系统线程的封装,将里面的状态进一步精细化了。
NEW
:系统中的线程还没创建出来,但是有个 Thread 对象RUNNABLE
: 就绪状态(1.正在CPU上执行 2.准备好随时可以去CPU上运行)TERMINATED
: 系统中的线程已经执行完了,Thread对象还在TIMED_WAITING
: 指定时间等待BLOCKED
:等待锁出现的状态WAITING
:使用 wait、join 方法出现的状态
相关文章:

Java 多线程系列Ⅰ(创建线程+查看线程+Thread方法+线程状态)
多线程基础 一、创建线程的五种方法前置知识1、方法一:使用继承Thread类,重写run方法2、方法二:实现Runnable接口,重写run方法3、方法三:继承Thread,使用匿名内部类4、方法四:实现Runnable&…...

无入侵接口文档smart-doc
Smart-doc优点: 1.非侵入式生成接口文档 2.减少接口文档的手动更新麻烦&保证了接口文档和代码的一致 3.随时可生成最新的接口文档 4.保持团队代码风格一致:smart-doc支持javadoc,必须按照这个才能生成有注释的接口文档 最终效果 1.导入依赖 <pl…...

nacos配置超级管理员账户,只能mysql存储数据(或者其他数据库)
nacos本身是不允许授权超级管理员账号的,也就是角色名“ROLE_ADMIN”。作者在页面上试过了,不必再次尝试改的方式是直接改数据库里面的数据...

【前端自动化部署】,Devops,CI/CD
DevOps 提到Jenkins,想到的第一个概念就是 CI/CD 在这之前应该再了解一个概念。 DevOps Development 和 Operations 的组合,是一种方法论,并不特指某种技术或者工具。DevOps 是一种重视 Dev 开发人员和 Ops 运维人员之间沟通、协作的流程。…...

【C语言】探讨蕴藏在表达式求解中的因素
🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:C语言 🔥该篇将探讨 操作符 和 类型转换 对表达式求解的影响。 目录: 隐式类型转换算术转换操作符的属性❤️ 结语 隐…...
【Flutter】Flutter 使用 video_player 播放视频
【Flutter】Flutter 使用 video_player 播放视频 文章目录 一、前言二、video_player 简介三、安装和配置四、基本使用五、完整示例 六、高级功能七、总结 一、前言 大家好,我是小雨青年,今天我要和大家分享一款非常实用的 Flutter 包——video_player。…...
如何使用 ChatGPT 快速制作播客和其他长篇内容
使用ChatGPT快速制作播客和其他长篇内容是一个高效且具有一定创造性的过程。以下是一些详细的步骤和技巧,以帮助你充分利用ChatGPT来制作高质量的内容。 一、准备阶段 确定主题或话题:在开始制作之前,你需要明确你的播客或长篇内容将聚焦的主…...

JavaScript基础语法02——JS书写位置
哈喽,大家好,我是雷工! 今天继续学习JavaScript基础语法,JS的书写位置,俗话说:好记性不如烂笔头,边学边记,方便回顾。 1、行内JavaScript 代码写在标签内部 示例: <…...
LInux快捷命令
切换到行头:ctrla 或者 ctrlhome 切换到行尾:ctrale 或者 ctrlend 光标向左切换一个单词:ctrl← 光标向右切换一个单词:ctrl→ 历史命令搜索:history 历史命令匹配第一条执行:!x (x表示历史命令…...

jvm的内存划分区域
jvm划分5个区域: java虚拟机栈、本地方法栈、堆、程序计数器、方法区。 各个区各自的作用: 1.本地方法栈:用于管理本地方法的调用,里面并没有我们写的代码逻辑,其由native修饰,由 C 语言实现。 2.程序计数…...

什么是数据中心IP,优缺点是什么?
如果根据拥有者或者说发送地址来分类的话,可以将代理分为三类:数据中心ip,住宅ip,移动ip 本文我们来了解数据中心ip的原理以及他们的优势劣势,才能选择适合自己的代理。 一、什么是数据中心ip代理? 数据中心ip是由数据中心拥有…...

模块化与组件化:开发中的双剑合璧
引言:模块化与组件化的重要性 在现代软件开发中,随着项目规模的增长和技术的复杂性增加,如何有效地组织和管理代码变得越来越重要。模块化与组件化作为两种主要的代码组织方法,为开发者提供了有效的工具,帮助他们创建…...

【C++初阶】list的常见使用操作
👦个人主页:Weraphael ✍🏻作者简介:目前学习C和算法 ✈️专栏:C航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞…...

排序之插入排序
文章目录 前言一、直接插入排序1、基本思想2、直接插入排序的代码实现3、直接插入排序总结 二、希尔排序1、希尔排序基本思想2、希尔排序的代码实现3、希尔排序时间复杂度 前言 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大…...

c# - - - 安装.net core sdk
如图,安装的是.Net Core 2.2版本 查看安装成功...

Golang Gorm 高级查询之where + find
插入测试数据 package mainimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm" )type Student struct {ID int64Name string gorm:"size:6"Age intEmail *string }func (*Student) TableName() string {return "student&q…...
【LeetCode】30 天 Pandas 挑战
一、笔记 1.对某列进行筛选 df[(df[column1]条件1) | (df[column2]条件2) & (df[column3]条件3)][[columns]]真题: (一)条件筛选——1.大的国家(一)条件筛选——2.可回收且低脂的产品(一)…...
头歌MYSQL——课后作业2 数据表中数据的插入、修改和删除
第1关:数据表中插入一条记录,对指定字段赋值 任务描述 本关任务:在library数据库的reader数据表中插入一条数据 姓名xm为林团团,电话号码dhhm为13507311234,其余字段取默认值 显示数据表的所有数据 为了完成本关任务,…...

Maven的profiles多环境配置
一个项目通常都会有多个不同的运行环境,例如开发环境,测试环境、生产环境等。而不同环境的构建过程很可能是不同的,例如数据源配置、插件、以及依赖的版本等。每次将项目部署到不同的环境时,都需要修改相应的配置,这样…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...