【多线程】初始线程和Thread类
一. 线程
1. 线程的引入

- 虽然进程已经可以解决并发编程这种问题,但是进程在频繁进行创建和销毁的时候,系统开销非常大,如果一个服务器向你发送多个请求,针对每一个请求,都需要创建一个进程来应答,每个进程都需要申请资源(从硬盘加载到内存)和释放资源, 导致了资源的浪费
- 服务器发送的多个请求,所需要的资源都是相同的,为了解决相同类型的问题而创建出多个进程,内存存放多个相同的资源,导致了内存资源的严重浪费
线程的出现解决‘分配资源’和”释放资源“带来的额外开销,同时还支持并发处理,被称为轻量级进程
2. 线程的概念

- 一个进程包含一个或者多个线程(线程是进程的更精细化分)
- 一个线程包含一个PCB(一个进程包含多个PCB)
- 一个进程里面的所有线程,共用一份资源
注意:多个线程的内存指针和文件描述符表都共用同一份(PCB中的内存指针都指向一个地址)
那么意味着创建第一个线程需要分配资源,后面的线程只需要使用第一个线程申请到的资源即可。
线程主要解决的问题:降低频繁申请资源和释放资源带来的开销(开销很小,并不是没有开销)
进程是资源分配的基本单位,线程是资源调度的基本单位
3. 线程和进程的区别
- 一个进程包含一个或者多个线程
- 每个线程都是单独的执行流,可以单独参与cpu的调度
- 一个进程分配一个资源,这个进程中的所有线程都共用这一个资源
- 进程是资源分配的基本单位,线程是资源调度的基本单位
- 进程和进程直接是相互独立的,彼此互不干扰
- 同一个进程中,线程和线程之间是相关的,一个线程抛出异常,可能会影响其他线程的工作(线程安全问题)
- 线程并不是越多越好,线程过少,资源利用率较低,线程过多,调度开销变大,要适中
四. 线程的创建
(1)继承Thread类
class MyThread extends Thread{//run方法是线程的入口@Overridepublic void run(){System.out.println("hello World");}
}public class Demo_1 {
// main方法是进程的入口public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();
// 调用start方法会调用系统API,在系统内核中创建出线程
// 意味着可以同时具备多个执行流}
}
需要重写run方法
(2)实现Runnable接口
class MyThread3 implements Runnable{@Overridepublic void run() {while(true){System.out.println("111");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Demo_3 {public static void main(String[] args) {MyThread3 myThread3 = new MyThread3();Thread t = new Thread(myThread3);
// Thread t = new Thread(new MyThread3());t.start();while(true){System.out.println("222");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
这种写法灵活性更强,让线程和要执行的任务进行解耦合,如果那个线程想要实现这个操作,直接调用这个类即可
(3)使用匿名内部类
public class Demo_4 {public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run() {while(true){System.out.println("111");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};thread.start();while(true){System.out.println("222");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
没有具体的实例,不知道子类的名称
(4)采用匿名内部类创建Runnable类
public class Demo_5 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("111");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while(true){System.out.println("222");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
(5)采用lambda(推荐写法)
public class Demo_6 {public static void main(String[] args) {Thread t = new Thread( ()-> {//()内是形参列表while(true){System.out.println("111");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();while(true){System.out.println("222");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
这里的()->括号内是形参类型
二. Thread类
在 Java 中,main函数是程序的入口点。当 Java 虚拟机(JVM)启动时,会创建一个主线程来执行main方法。
public static void main(String[] args) {System.out.println("hello");}
- 一个进程中至少包含一个线程,第一个线程被称为主线程
(1)初始Thread类
在 Java 中,Thread 类是用于创建和管理线程的类,它位于java.lang 包下,所以在使用的时候不需要导入 ,我们可以通过这个类,来创建一个进程
public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {System.out.println("hello");}};t.start();System.out.println("222");}

注意:
- run方法是线程的入口,想要线程实现的功能要写入run方法中
- Thread t = new Thread();是创建出一个实例,但是内核中还没有PCB,无法参与调度
- t.start()是启动线程,在内核中创建出PCB,参与调度
- 每一个线程都是独立的执行流,这两个代码是并发执行的
- 所有的进程地位都是平等的,进程的执行顺序是随机的(抢占式执行)
- 为什么先输出“222”,因为创建一个线程的开销很小,但并不是没有,会花费很少的一段时间,所有大概率会先输出主线程的内容
(2)常用构造方法
| 方法 | 说明 |
|---|---|
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
1. 创建线程对象
Thread thread = new Thread();
2. 使用 Runnable
创建一个新的线程对象,并指定一个实现了Runnable接口的任务。
class MyThread3 implements Runnable{@Overridepublic void run() {System.out.println("111");}
}
public class Demo_2 {public static void main(String[] args) {MyThread3 myThread3 = new MyThread3();Thread t = new Thread(myThread3);}
}
3. 创建线程对象,并命名
Thread thread1 = new Thread("线程1");
注意:命名对线程不会起到影响,只是方便测试
4. 使用 Runnable 并命名
创建一个新的线程对象,并指定一个实现了Runnable接口的任务。
class MyThread3 implements Runnable{@Overridepublic void run() {System.out.println("111");}
}
public class Demo_2 {public static void main(String[] args) {MyThread3 myThread3 = new MyThread3();Thread t = new Thread(myThread3,"线程001");}
}
(3)常用方法
| 属性 | 获取方法 |
|---|---|
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
- ID:jvm自动分配身份标识符,确保唯一性
- 名称:如果没有命名,会自己分配一个名称,用于区别
- 状态:常见有阻塞状态,运行状态,就绪状态等
- 优先级:可以设置优先级,对内核调度器的调度起到一些影响,主要还是随机调度
- 后台程序:不会阻止线程的结束
- 前台程序:会阻止线程的结束(一般我们创建的代码就是前台代码,会因为运行导致进程不能结束)
- 存活:创建出一个实例,不是存活,只有start后(在内核中创建出PCB可以进行调度),才是存活,线程执行完毕,内核中PCB被释放,即使实例还存在,也不是存活
public class Demo_9 {public static void main(String[] args) {Thread thread = new Thread(()->{});thread.start();System.out.println(thread.getId());System.out.println(thread.getName());System.out.println(thread.getState());System.out.println(thread.getPriority());System.out.println(thread.isDaemon());System.out.println(thread.isAlive());System.out.println(thread.isInterrupted());}
}

(4)启动线程
使用start方法,就是启动线程,在内核中创建出PCB可以进行调度
对于同一个Thread对象来说,start只能调用一次
start方法和run方法区别
start方法会创建一个新线程,run方法不会创建新线程,只是定义线程任务。
(5)终止线程
让run方法结束,线程任务完成就会终止线程,依赖run方法的代码逻辑
1. 通过共享的标记位
//通过设置一个变量,手动控制
public class Demo_7 {//必须写外面,如果写在主线程里面,会发生报错,lambda只能接受不能被修改的值static boolean flt = false;//成员变量可以,变成了内部类访问外部类成员public static void main1(String[] args) {Thread t = new Thread(()->{while(!flt){System.out.println("这是一个正在运行的线程……");}System.out.println("线程运行结束……");});t.start();// 主线程进行干扰flt = true;System.out.println("让线程结束……");
// 交换代码位置,可能会导致顺序错误
// 交换之后,可能会导致再次打印这是一个正在运行的线程}}
注意:共享的标志位要写在外面,因为不同的线程在不同的栈帧中运行,导致生命周期不一样,如果主线程栈帧销毁了,flt 变量不存在了,但是其他的线程的栈帧还在,想使用flt变量,会发生代码异常
通过改变flt的属性,来影响进程
2. 调用 interrupt() 方法
public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {//默认为falseSystem.out.println("这是一个正在运行的线程……");try {Thread.sleep(1000);} catch (InterruptedException e) {
// throw new RuntimeException(e);//报错并停止运行,后面代码不会执行e.printStackTrace();//显示错误原因break;//跳出循环,继续向下执行}}System.out.println("线程执行完毕");});thread.start();Thread.sleep(3000);thread.interrupt();System.out.println("让进程停下");}
Thread.currentThread()获取当前的引用,isInterrupted()是判定标志位,interrupt()是修改标志位
注意:这里会发生异常,如果进程在处于休眠状态,interrupt()会将sleep提前唤醒,会执行异常处理(1.报错并停止运行 2. 提示错误原因继续向下运行 3.什么也不做继续向下运行)
其实还有一种强制终止线程,不管线程的死活,直接干掉他,但是java中不支持,因为如果线程执行了一半,直接干掉他,会产生一些垃圾数据(加载一半的错误信息等)
(6)等待线程
线程之间的执行顺序是完全随机的(随机调度,抢占式执行),我们也不清楚线程执行多长时间(如果知道执行时间,可以根据休眠,控制线程执行的顺序),但是我们可以使用join方法控制结束的顺序
| 方法 | 说明 |
|---|---|
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
| public void join(long millis, int nanos) | 同理,但可以更高精度 |
注意:
- Join方法不是确定线程的执行顺序,而是确定线程的结束顺序
- 通过阻塞操作,让两个进程的结束时间产生了先后顺序
- 如果A线程中调用了Join方法,就是A线程进入阻塞状态
1. 死等
thread.join();
这种等待属于死等,只要这个进程不结束,就一直处于阻塞状态(死等)
2. 设置等待的时间
thread.join(100);
一般不会使用死等,万一代码出现故障,就会导致后面的代码不能工作(死机)
举例:使用多个进程并发进行一系列的计数
public class Demo_8 {public static int result = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 100; i++) {result +=i;}});thread.start();// 解决方法1:Thread.sleep(100);
// 弊端:如果不知道thread线程要执行多久,休眠0.1秒可能会少// 解决方法2:thread.join();
// 等thread线程执行完了,才会执行主线程System.out.println("结果:"+result);
// 因为执行的顺序不确定
// 结果可能出现3种,1,结果为0,2.结果为中间数,3.结果为4950}
}
- 休眠的方式,控制执行顺序(前提是知道执行时间,才能设置休眠时间)
- 死等的方式,等thread线程执行完了,才可以执行主线程的打印操作
(7)获取线程引用
Thread.currentThread()
这个方法返回当前线程对象的引用
如果是继承Thread,那么可以使用this拿到线程的实例
class MyThread extends Thread{//run方法是线程的入口@Overridepublic void run(){System.out.println("hello World");System.out.println(this.getId());}
}
如果是实现接口或者lambda的方式,this没有用,因为在静态方法中,this和进程实例没有直接的关联,不能用于获取进程实例。
(8)休眠线程
| 方法 | 说明 |
| sleep(long millis) | 使当前线程暂停执行指定的毫秒数 |
try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}
sleep方法:会让线程从就绪状态调度到阻塞状态,不参与调度,sleep结束,会从阻塞状态调度到就绪状态
三. 线程的状态
java中,线程的常见状态

- NEW:线程的实例创建成功,但是没有调用start方法在内核中创建线程
- TERMINATED:线程的实例还存在,但是线程已经执行完毕,内核中的PCB已销毁
- RUNNABLE:就绪状态,说明这个线程正在cpu上执行,或者准备就绪随时可以在CPU上执行
- WAITING:阻塞状态(Join或者wait),不带时间的阻塞(死等)
- TIMED_WAITING:阻塞状态,带时间的阻塞
- BLOCKED:由于锁竞争引起的阻塞
public class Demo_10 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("111");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}});// 运行前System.out.println(thread.getState());thread.start();
// 运行后System.out.println(thread.getState());// 休眠后Thread.sleep(3000);System.out.println(thread.getState());// 结束后thread.interrupt();thread.join();System.out.println(thread.getState());}
}
在线程的运行中,主要状态是:NEW --> RUNNABLE --> TERMINATED
会因为执行一些特殊的操作,进入阻塞状态,进入阻塞的方式不同,被唤醒的方式也可能不同。
点赞的宝子今晚自动触发「躺赢锦鲤」buff!
相关文章:
【多线程】初始线程和Thread类
一. 线程 1. 线程的引入 虽然进程已经可以解决并发编程这种问题,但是进程在频繁进行创建和销毁的时候,系统开销非常大,如果一个服务器向你发送多个请求,针对每一个请求,都需要创建一个进程来应答,每个进程…...
WebLogic中间件常见漏洞
一、后台弱⼝令GetShell 1.环境搭建 cd vulhub-master/weblogic/weak_password docker-compose up -d 2.访问网站并登陆后台 /console/login/LoginForm.jsp 默认账号密码:weblogic/Oracle123 3.点击部署,点击安装ÿ…...
[笔记.AI]多头自注意力机制(Multi-Head Attention)
多头自注意力是深度学习领域,特别是自然语言处理(NLP)和Transformer模型中的关键概念。其发展源于对序列数据中复杂依赖关系的建模需求,特别是在Transformer架构的背景下。 举例 比喻-读长篇文章 用一个简单的比喻来理解“多头注…...
【基于ROS的A*算法实现路径规划】A* | ROS | 路径规划 | Python
### 记录一下使用Python实现ROS平台A*算法路径规划 ### 代码可自取 :Xz/little_projecthttps://gitee.com/Xz_zh/little_project.git 目录 一、思路分析 二、算法实现 三、路径规划实现 一、思路分析 要求使用A*算法实现路径规划,可以将该任务分为三…...
keda基于postgresql伸缩dify-api服务
1 概述 dify-api使用postgresql来存储数据,在dify控制台每新建一个聊天机器的聊天框,就会在conversations表里新插入一条记录,并且不断地更新字段updated_at,示例如下: dify# select * from conversations limit 1; …...
趣味极简品牌海报艺术贴纸设计圆润边缘无衬线粗体装饰字体 Chunko Bold - Sans Serif Font
Chunko Bold 是一种功能强大的显示字体,体现了大胆极简主义的原则 – 当代设计的主流趋势。这种自信的字体将粗犷的几何形状与现代的趣味性相结合,具有圆润的边缘和强烈的存在感,与当今的极简主义设计方法完美契合。无论是用于鲜明的构图还是…...
VoLTE(Voice over Long-Term Evolution)
VoLTE,即Voice over Long-Term Evolution,是一种基于4G LTE网络的高质量语音通话技术。与传统的2G和3G网络中的语音通话不同,VoLTE将语音信号转换为数据包,通过LTE网络进行传输,从而实现了更快的连接速度和更高的通话质…...
指针,数组 易混题解析(一)
目录 一.相关知识点 1.数组名是什么? 两个例外: 2.strlen 3.sizeof 4. * ( ) 与 [ ] 的互换 二.一维数组 三.字符数组 1. 字符 (1)sizeof (2)strlen 2.字符串 (1)si…...
Java 基础篇:数组
前言 数组(Array)是 Java 中最基本的数据结构之一,它用于存储相同类型的元素,并且在内存中是连续存储的。数组具有高效的索引访问特点,但长度固定,不能动态调整。 本文将介绍数组的基本概念、声明和初始化方…...
从汽车 BCM 方案看国产 MCU 芯片的突围与挑战
摘要 :汽车车身控制模块(BCM)作为汽车电子系统的核心控制单元,其性能高度依赖于微控制单元(MCU)芯片。随着汽车智能化与电动化的发展,国产 MCU 芯片在 BCM 领域的应用逐渐扩大。本文结合行业数据…...
深入理解 Spring 框架中的 IOC 容器
一、Spring 框架概述 Spring 框架是一个轻量级的 Java 开发框架,由 Rod Johnson 在 2003 年创建。它的诞生旨在简化企业级应用开发的复杂性。Spring 框架提供了诸如 IoC(控制反转)和 AOP(面向切面编程)等核心功能&…...
深入理解 Java 中 instanceof 操作符
目录 1. instanceof 的基本用法 1.1 语法 1.2 示例 2. instanceof 的用途 2.1 类型检查 2.2 类型转换 2.3 多态编程 3. instanceof 的注意事项 3.1 null 检查 3.2 接口检查 3.3 继承关系 3.4 性能问题 4. instanceof 代码示例 4.1 多态处理 4.2 接口检查 4.3 n…...
2025前端面试题记录
vue项目目录的执行顺序是怎么样的? 1、package.json 在执行npm run dev时,会在当前目录寻找package.json文件,此文件包含了项目的名称版本、项目依赖等相关信息。 2、webpack.config.js(会被vue-cli脚手架隐藏) 3、vue.config.js 对…...
复变函数摘记2
复变函数摘记2 3. 级数3.1 复数项级数3.2 复变幂级数3.3 泰勒级数3.4 洛朗级数 3. 级数 \quad 复数项级数的一般项 α n a n i b n \alpha_na_n\text{i}b_n αnanibn 为复数,与高等数学中无穷级数的分析方式类似,也是通过和函数来研究级数的收敛…...
光纤的频率和带宽
光纤通信中的频率和带宽涉及光波的物理特性以及通信系统的设计,以下是详细解释: 1. 光纤的工作频率 光纤通信利用光波作为载波,工作频率主要在近红外波段,具体频段和对应的波长如下: C波段(Conve…...
高频面试题(含笔试高频算法整理)基本总结回顾67
干货分享,感谢您的阅读! (暂存篇---后续会删除,完整版和持续更新见高频面试题基本总结回顾(含笔试高频算法整理)) 备注:引用请标注出处,同时存在的问题请在相关博客留言…...
Kafka--常见问题
1.为什么要使用 Kafka,起到什么作用 Kafka是一个高吞吐量、分布式、基于发布订阅的消息系统,它主要用于处理实时数据流 Kafka 设计上支持高吞吐量的消息传输,每秒可以处理数百万条消息。它能够在处理大量并发请求时,保持低延迟和…...
优选算法的睿智之林:前缀和专题(一)
专栏:算法的魔法世界 个人主页:手握风云 目录 一、前缀和 二、例题讲解 2.1. 一维前缀和 2.2. 二维前缀和 2.3. 寻找数组的中心下标 2.4. 除自身以外数组的乘积 一、前缀和 前缀和算法是一种用于处理数组或序列数据的算法,其核心思想是…...
嵌入式八股文学习——STL相关内容学习
文章目录 map和set的区别与实现1. map和set的区别2. 为什么set的元素和map的key不可修改? map和set的实现1. map的实现原理map的操作:map的特点: 2. set的实现原理set的操作:set的特点: map和set的底层原理(…...
【清华大学】AIGC发展研究(3.0版)
目录 AIGC发展研究报告核心内容一、团队简介二、AI哲学三、国内外大模型四、生成式内容(一)文本生成(二)图像生成(三)音乐生成(四)视频生成 五、各行业应用六、未来展望 AIGC发展研究…...
JavaSE1.0(基础语法之运算符)
算术运算符 基础运算之加 减 乘 除 取余( - * / %) 运算符之相加( ) public static void main(String[] args) {System.out.println("Hello world!");int a 10;int b 20;int c a b;System.out.println(c);//…...
二十五、实战开发 uni-app x 项目(仿京东)- 前后端轮播图
定义了一个名为 Swiper 的Java类,用于表示一个轮播图实体。它使用了 Jakarta Persistence API (JPA) 来映射数据库表,并使用了 Lombok 库来简化代码。以下是对代码的详细讲解: 1. 包声明 package com.jd.jdmall.model; 这行代码声明了该类所在的包路径为 com.jd.jdmall.mode…...
ubuntu设置开机自动运行应用
系统版本:Ubuntu 24.04.1 LTS桌面版 按招网上的资料显示,当前版本主要的实现方式有以下两种, 方式1:通过图形界面的【启动应用程序】设置开机自启动;方式2:配置为服务实现开机自启动。 但是在我的电脑上方…...
蓝桥与力扣刷题(蓝桥 数的分解)
题目:把 2019分解成 3个各不相同的正整数之和,并且要求每个正整数都不包含数字 2 和 4,一共有多少种不同的分解方法? 注意交换 3 个整数的顺序被视为同一种方法,例如 1000100118和 1001100018 被视为同一种。 解题思…...
用ACM模式模板刷hot100
面试手撕给的模板基础上写 给的模板一般是下面这样 把while内容删除(一般刷hot100题目输入不需要同时输入几组) 第一个方法里写处理输入输出 自己再写一个方法,就是力扣里的核心代码(加上static) 第一个处理输入输…...
Java IO 流:从字节到字符再到Java 装饰者模式(Decorator Pattern),解析与应用掌握数据流动的艺术
在 Java 编程中,IO(输入输出)流是处理数据输入输出的核心工具。无论是读取文件、网络通信,还是处理用户输入,IO 流都扮演着重要角色。本文将深入探讨 Java IO 流的核心概念、分类、经典代码实例及其应用场景࿰…...
爬虫案例-爬取某站视频
文章目录 1、下载FFmpeg2、爬取代码3、效果图 1、下载FFmpeg FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。 点击下载: ffmpeg 安装并配置 FFmpeg 步骤: 1.下载 FFmpeg: 2.访问 FFmpeg 官网。 3.选择 Wi…...
nacos-未经授权创建用户漏洞
1、修改配置文件 vim application.properties# 修改配置项 nacos.core.auth.enabledtrue nacos.core.auth.enable.userAgentAuthWhitefalse2、重启nacos systemctl restart nacos3、验证 打开nacos部署服务器输入命令 curl -XPOST -d “usernametest123&passwordtest!123…...
C++:IO库
一、C IO库的架构 C标准库中的IO系统基于流(Stream)的概念,分为三层结构: 流对象(如cin, cout, fstream)流缓冲区(streambuf,负责底层数据处理)数据源/目的…...
企业级前端架构设计与实战
一、架构设计核心原则 1.1 模块化分层架构 典型目录结构: src/├── assets/ # 静态资源├── components/ # 通用组件├── pages/ # 页面模块├── services/ # API服务层├── store/ # 全局状态管理├── uti…...
