30天学Java第九天——线程
并行与并发的区别
- 并行是多核 CPU 上的多任务处理,多个任务在同一时间真正的同时执行
- 并发是单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,用于解决 IO 密集型任务的瓶颈
线程的创建方式
Thread 的构造方法,可以在创建线程的时候为线程起名字

- 第一种方法:继承 Thread 类
- 第一步:编写一个类继承 Thread
- 第二步:重写 run 方法
- 第三步:new 线程对象
- 第四步:调用线程对象的 start 方法,启动线程
一定调用的是 start 方法,不是 run 方法。start 方法的作用就是启动一个线程,线程启动完成该方法就结束。
public class MyThread {public static void main(String[] args) {NewThread nt = new NewThread();// 启动 start 方法启动线程,而不是 run 方法nt.start();for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}class NewThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}
对比一下 run 方法与 start 方法的内存图
调用 run 方法的内存图
调用 run 方法并没有启动新线程,代码都是在 main 方法中执行的,内存只有一个主线程的栈,因此必须 run 方法中的代码执行玩,才能执行后续的代码

调用 start 方法的内存图
调用 start 方法会启动一个新线程,内存会分配一个新的栈空间给新线程(分配完成start方法就结束了,main 方法中的代码继续向下执行),新线程的代码在新的栈空间执行,main 方法的代码在 main 方法的栈空间执行,两个线程抢夺 CPU 时间片交替执行。

2. 第二种方法:实现 Runnable 接口
- 第一步:编写一个类实现 Runnable 接口
- 第二步:实现接口中的 run 方法
- 第三步:new 线程对象
使用Thread的带有 Runnable 参数的构造方法创建对象

- 第四步:调用线程对象的 start 方法,启动线程
推荐使用这种而不是第一种,因为第一种方式使用继承,而Java只能单继承,因此失去了继承其他类的能力,而 实现 Runnable 接口则还能继承其他类
- 实现 Callable 接口
- 第一步:编写一个类实现 Callable 接口
- 第二步:实现接口中的 run 方法
- 第三步:new 线程对象
- 第四步:调用线程对象的 start 方法,启动线程
线程常用的三个方法
final String getName():返回此线程的名称。final void setName(String name):将此线程的名称更改为等于参数 name 。static Thread currentThread():返回对当前正在执行的线程对象的引用。
线程七个生命周期
- NEW:新建状态
当线程被创建但尚未开始运行时,它处于新建状态。在这个阶段,线程对象被实例化,但尚未调用 start() 方法。 - RUNNABLE:可运行状态
- 就绪状态
一旦调用了 start() 方法,线程进入就绪状态。在这个状态下,线程准备好运行,并等待线程调度程序的分配。此状态并不一定代表线程正在运行,可能会处于等待获取CPU时间片的状态。 - 运行状态
线程获得CPU资源并开始执行其任务时,线程进入运行状态。在这个状态下,线程执行其代码。
- 就绪状态
- BLOCKED:阻塞状态
当线程尝试获取一个已经被其他线程持有的锁时,它会进入阻塞状态。在这个状态下,线程无法继续执行,直到它获得所需的锁。 - WAITING:等待状态
如果线程调用 wait()、join() 或 LockSupport.park() 方法,它会进入等待状态。在这种状态下,线程会等待其他线程通知或唤醒它。 - TIMED_WAITING:超时等待状态
线程在等待的同时设置了时间限制(例如,调用 sleep(milliseconds) 或 wait(milliseconds)),将进入超时等待状态。如果在超时时间到达之前线程未被唤醒,则该线程会返回到就绪状态。 - TERMINATED:终止/死亡状态
当线程的 run() 方法执行完毕或者因异常终止时,线程进入终止状态。在这个状态下,线程完成了它的生命周期,无法重新启动。
线程常用的调度方法
- start()
用于启动线程,使其进入就绪状态。 - sleep(long millis)
使当前正在执行的线程暂停指定的时间,被调用的线程会进入阻塞状态,在指定的毫秒数后,线程会回到就绪状态。 - yield()
暂时让出当前线程的执行权,该方法提示线程调度器允许其他同等优先级的线程获得执行时间。
并不保证在调用后立即释放控制权。
让位后的线程进入就绪状态,并不会阻塞。 - join()
等待一个线程完成,如果线程 A 调用线程 B 的 join() 方法,线程 A 会阻塞,直到线程 B 执行完毕并终止。
join(long millis) 方法也可以指定时间,指的是加入 A 线程的时间或者说阻塞 A 线程的时间,时间一到就退出。如果在指定的 millis 时间内,B 线程结束了,被阻塞的 A 线程也会结束阻塞状态。 - interrupt()
发送一个中断信号给线程。如果线程正处于等待、睡眠或阻塞状态,将会抛出InterruptedException。抛出异常就会导致线程退出等待、睡眠或阻塞状态,从而达到中断的目的,利用了异常的机制。 - setPriority(int newPriority)
设置线程的优先级。线程的优先级是一个整数值,范围从1(最低)到10(最高)。这并不保证线程会按优先级执行,但可以指示调度器的优先级。 - wait() 和 notify()
用于线程间的通信和协作。wait() 使线程在对象监视器上等待,直到其他线程调用 notify() 或 notifyAll() 来唤醒它。
如何强制结束一个线程
Thread.stop() 方法可以强制结束线程,但是在 Java1.2 之后就被弃用了,因为使用 stop() 方法会导致线程立即终止,这可能导致锁未解锁、文件未正确关闭等情况,因此强烈不推荐。更好的办法是使用标志位或者 interrupt() 方法。
- 设置标志位方法
通过使用一个共享的标志位来控制线程的生命周期是推荐的做法。主线程可以通过设置标志位来通知子线程应停止执行。class CustomThread extends Thread { private volatile boolean running = true; // 使用 volatile 关键字确保可见性 public void run() { while (running) { // 执行任务 System.out.println("Thread is running..."); try { Thread.sleep(500); // 模拟工作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 } } System.out.println("Thread is stopping..."); } public void stopRunning() { running = false; // 设置标志位 } } public class Main { public static void main(String[] args) throws InterruptedException { CustomThread thread = new CustomThread(); thread.start(); Thread.sleep(2000); // 让线程运行2秒 thread.stopRunning(); // 请求线程停止 thread.join(); // 等待线程结束 System.out.println("Main thread finished."); } } - 使用 interrupt() 方法
在Java中,interrupt() 方法可以用于中断一个线程。线程在被中断时可以选择捕捉异常或者检查中断状态,从而优雅地结束自己。class InterruptibleThread extends Thread { public void run() { try { while (!Thread.currentThread().isInterrupted()) { // 执行任务 System.out.println("Thread is running..."); Thread.sleep(500); // 模拟工作 } } catch (InterruptedException e) { // 捕捉到中断异常,线程可以选择停止 System.out.println("Thread was interrupted."); Thread.currentThread().interrupt(); // 重新设置中断状态 } System.out.println("Thread is stopping..."); } } public class Main { public static void main(String[] args) throws InterruptedException { InterruptibleThread thread = new InterruptibleThread(); thread.start(); Thread.sleep(2000); // 让线程运行2秒 thread.interrupt(); // 中断线程 thread.join(); // 等待线程结束 System.out.println("Main thread finished."); } }
守护线程
在 Java 中,线程被分为两大类,一类是用户线程,一类是守护线程。
在 JVM 中,有一个隐藏的守护线程就是 GC 线程
守护线程的特点:
- 后台运行: 守护线程通常是后台执行的,用于执行一些辅助任务,比如垃圾回收、线程池中的工作线程等。
- 生命周期受限: JVM 会在所有非守护线程结束后自动结束守护线程。如果没有非守护线程在运行,JVM会退出。
- 优先级: 守护线程的优先级与普通线程相同,但它们的作用通常是协助非守护线程。
如何创建守护线程
三个步骤创建守护线程:
- 创建一个线程实例
- 调用
setDaemon(true)方法,将该线程设置为守护线程 - 启动线程
class DaemonThread extends Thread { @Override public void run() { while (true) { System.out.println("Daemon thread is running..."); try { Thread.sleep(1000); // 模拟一些工作 } catch (InterruptedException e) { System.out.println("Daemon thread interrupted."); } } }
} public class Main { public static void main(String[] args) { Thread daemonThread = new DaemonThread(); daemonThread.setDaemon(true); // 设置为守护线程 daemonThread.start(); try { Thread.sleep(3000); // 主线程睡眠3秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main thread is ending..."); // 主线程结束,Daemon线程会随之结束 }
}
定时任务
在Java中,Timer 是一个用于调度任务的工具类,允许开发者在指定的时间间隔内重复执行任务或在特定的时间点执行任务。Timer 类通常与 TimerTask 类一起使用,其功能足够简单,适合于许多基本的定时任务需求。
Timer: 一个定时器,负责调度任务。

TimerTask: 一个抽象类,所有需要被调度的任务都需要继承这个类并重写 run() 方法。

创建定时任务
使用 Timer 和 TimerTask 来创建定时任务的基本步骤如下:
- 创建一个 Timer 实例。
- 创建一个继承自 TimerTask 的类,并实现 run() 方法。
- 使用 Timer 的 schedule() 或 scheduleAtFixedRate() 方法将任务和执行时间关联。
常用方法
- schedule(TimerTask task, long delay): 在指定的延迟后调度任务。
- schedule(TimerTask task, Date time): 在指定的时间执行任务。
- schedule(TimerTask task, long delay, long period): 设定任务在指定的延迟后每隔一个时间段再次执行。
- scheduleAtFixedRate(TimerTask task, long delay, long period): 类似于 schedule,但是以固定率运行,适用于需要固定间隔执行的任务。
import java.util.Timer;
import java.util.TimerTask; public class TimerExample { public static void main(String[] args) { Timer timer = new Timer(); // 创建定时器 TimerTask task = new TimerTask() { @Override public void run() { System.out.println("Task executed at: " + System.currentTimeMillis()); } }; // 在延迟 1 秒后执行任务,每隔 2 秒重复执行 timer.scheduleAtFixedRate(task, 1000, 2000); // 主线程睡眠 10 秒,以便观察输出 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // 取消定时器 timer.cancel(); System.out.println("Timer canceled."); }
}
注意事项
- 单线程中: Timer 是单线程的,如果一个 TimerTask 执行时间超过下一个任务的调度时间,后续的任务会被延迟执行。
- 异常处理: 如果 TimerTask 中的代码抛出未处理的异常,Timer 将停止执行所有后续任务。应确保 run() 方法内的代码是异常安全的。
- 使用 ScheduledExecutorService: 对于更复杂的定时任务需求(例如线程池,多线程调度等),建议使用 ScheduledExecutorService,它提供了更强大的功能和灵活性。
import java.util.concurrent.*; public class ScheduledExecutorExample { public static void main(String[] args) { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); Runnable task = () -> { System.out.println("Task executed at: " + System.currentTimeMillis()); }; // 在延迟 1 秒后执行,每隔 2 秒重复执行 scheduler.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); // 主线程睡眠 10 秒,以便观察输出 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // 关闭调度器 scheduler.shutdown(); System.out.println("Scheduler shut down."); } } - 开发中一般不使用 Timer ,有一些更好的框架可以设置定时任务。
线程优先级
- 线程是可以设置优先级的,优先级高的,获得CPU时间片的概率会高一些
- JVM 采用的是抢占式调度模式。谁的优先级高,获取 CPU 的概率就会高
- 默认情况下,一个线程的优先级是 5
- 线程优先级最低是 1,最高是 10
- Thread 类的字段属性
可以通过 MAX_PRIORITY 和 MIN_PRIORITY,设置最高最低优先级

线程安全
什么情况下需要考虑线程安全问题?
- 多线程的并发环境下
- 有共享的数据
- 共享数据涉及到修改操作
一般情况下,局部变量不存在线程安全问题,实例变量和静态变量可能存在线程安全问题。因为局部遍历存储在栈中,实例变量和静态变量存储在堆中,栈每个线程使用自己的,而堆是多线程共享的。
相关文章:
30天学Java第九天——线程
并行与并发的区别 并行是多核 CPU 上的多任务处理,多个任务在同一时间真正的同时执行并发是单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,用于解决 IO 密集型任务的瓶颈 线程的创建方式…...
kotlin的takeIf使用
takeIf用于判断指定对象是否满足条件,满足就返回该对象自身,不满足返回null。因为可以返回对象自身,所以可以用作链式调用,以简化代码,又因takeIf可能返回空,所以常常和let结合使用,示例如下&am…...
计算机毕业设计指南
哈喽各位大四的小伙伴们,以下是一份详细的计算机专业毕业设计指南,涵盖选题、流程、技术选型、开发建议和常见问题解决方案,帮助你高效完成毕业设计,如有其他问题,欢迎点击文章末尾名片进行咨询,可免费赠送…...
自行搭建一个Git仓库托管平台
1.安装Git sudo apt install git 2.Git本地仓库创建(自己选择一个文件夹) git init 这里我在 /home/test 下面初始化了代码仓库 1. 首先在仓库中新建一个txt文件,并输入一些内容 2. 将文件添加到仓库 git add test.txt 执行之后没有任何输…...
LeeCode 409.最长回文串
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的 回文串 的长度。 在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。 示例 1: 输入:s "abccccdd" 输出:7 解释: 我们可以构造的…...
ARM处理器内核全解析:从Cortex到Neoverse的架构与区别
ARM处理器内核全解析:从Cortex到Neoverse的架构与区别 ARM作为全球领先的处理器架构设计公司,其内核产品线覆盖了从高性能计算到低功耗嵌入式应用的广泛领域。本文将全面解析ARM处理器的内核分类、架构特点、性能差异以及应用场景,帮助读者深…...
【前端分享】JavaScript异步编程详解!
JavaScript 的异步编程是其核心特性之一,主要用于处理非阻塞操作(如网络请求、文件读写、定时任务等)。由于 JavaScript 是单线程的,异步机制可以避免代码阻塞,提高性能和用户体验。以下是 JavaScript 异步编程的核心概…...
工厂模式(简单工厂,工厂方法,抽象工厂)
工厂模式 工厂模式是java中最常用的设计模式,主要是用来完成对象的创建,使得对象创建过程和对象使用过程分离。 简单来说是取消对象创建者和使用者的耦合,简化new 对象的创建。 优势 :对象的属性创建完整。 缺点: 创建…...
Unity Internal-DeferredShading 分析
1. 延迟渲染的原理 延迟渲染主要包含了两个Pass。在第一个Pass中,我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息存储到G缓冲区…...
嵌入式面试题:C 语言核心考点经典例题
引言 在嵌入式系统开发的面试中,常常会考察候选人对 C 语言基础知识的掌握程度。以下将详细分析几道常见的嵌入式面试题,包括解题步骤、涉及的知识点以及相关拓展。 题目 1 main() {unsigned char z0;unsigned char x100;unsigned char y10;z (~x)*(…...
Axure RP9教程 【数据传输】(页面值传递)| 作用域 :全局变量、局部变量
文章目录 引言作用域:全局变量作用域>局部变量作用域I 基于全局变量实现一个简单的登陆操作设置变量值的交互动作打开链接的交互动作接收并显示变量值注意点see also共享原型引言 全局变量在交互效果作用是页面值传递 作用域:全局变量作用域>局部变量作用域 全局变量…...
IBM Rational Software Architect安装感受及使用初体验
1 安装感受 最近准备用UML 2.0绘制模型图。在读UML创始人之一Grady Booch写的书《Object-Oriented Analysis and Design with Applications》(第3版)1时,发现书中用的UML工具之一为IBM Rational Software Architect(RSAÿ…...
VRRP学习
虚拟路由器冗余技术【三层技术】:网关冗余VRRP设计了VRRP组的概念,在一个 VRRP 组中,多台路由器共同构成一个虚拟路由器。这个虚拟路由器拥有一个虚拟 IP 地址【VRRP-ID默认是8位二进制,范围是0~255,用以标识和区别不同…...
C_内存 内存地址概念
1. 计算机内存的基本概念 计算机的内存(RAM,随机存取存储器)是用来存储程序运行时的数据和指令的地方。内存被划分为许多小单元,每个单元有一个唯一的地址,这些地址从0开始编号。 内存单元:每个内存单元通…...
GPT-5、o3和o4-mini即将到来
原计划有所变更: 关于我们应有何期待的一些零散想法。 深度研究(Deep Research)确实强大但成本高昂且速度较慢(当前使用o3模型)。即将推出的o4-mini在性能上可能与o3相近,但将突破这些限制,让全球用户——甚至免费用户(尽管会有速率限制)——都能用上世界顶级AI研究助…...
C#MVC项目引用Swagger的详细步骤
目录 一、安装Swagger依赖包二、配置Swagger服务三、启用XML注释四、调整启动配置五、验证与访问常见问题解决 以下是基于ASP.NET Core项目集成Swagger的详细步骤(已适配当前项目结构): 一、安装Swagger依赖包 通过NuGet安装 右键点击项目…...
golang 对象池sync.Pool
Golang中的sync.Pool是什么? sync.Pool 是 Go 标准库中提供的一个对象池(Object Pool)实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收(GC)的压力。它的主要特点是: 临时对…...
聚焦AI与大模型创新,紫光云如何引领云计算行业快速演进?
【全球云观察 | 科技热点关注】 随着近年来AI与大模型的兴起,云计算行业正在发生着一场大变局。 “在2025年春节期间,DeepSeek两周火爆全球,如何进行私域部署成了企业关心的问题。”紫光云公司总裁王燕平强调指出,AI与…...
去重新闻数据中重复的正文内容(body 字段),并把唯一的新闻内容保存到一个新的 JSON 文件中
示例代码: import os import json import nltk from tqdm import tqdmdef wr_dict(filename,dic):if not os.path.isfile(filename):data []data.append(dic)with open(filename, w) as f:json.dump(data, f)else: with open(filename, r) as f:data json.l…...
解决前后端时区不一致问题
前后端时区不一致导致: 》数据不显示在前端 》页面显示时间有误 》一些对时间有要求的方法,无法正确执行,出现null值,加上我们对null值有判断/注解,程序就会报错中断,以为是业务逻辑问题,其实…...
有哪些反爬机制可能会影响Python爬取视频?如何应对这些机制?
文章目录 前言常见反爬机制及影响1. IP 封禁2. 验证码3. 请求头验证4. 动态加载5. 加密与混淆6. 行为分析 应对方法1. 应对 IP 封禁2. 应对验证码3. 应对请求头验证4. 应对动态加载5. 应对加密与混淆6. 应对行为分析 前言 在使用 Python 爬取视频时,会遇到多种反爬…...
STL之序列式容器(Vector/Deque/List)
序列式容器 序列式容器包括:静态数组 array 、动态数组 vector 、双端队列 deque 、单链表 forward_ list 、双链表 list 。这五个容器中,我们需要讲解三个 vector 、 deque 、 list 的使 用,包括:初始化、遍历、尾部插入与删除、…...
小试牛刀-抽奖程序
编写抽奖程序 需求:设计一个抽奖程序,点击抽奖按钮随机抽取一个名字作为中奖者 目标:了解项目结构,简单UI布局,属性方法、事件方法,程序运行及调试 界面原型 待抽奖: 点击抽奖按钮&#x…...
Vue 3 中 ref 与 reactive 的对比
Vue 3 中 ref 与 reactive 的对比 Vue 3 中 ref 与 reactive 的对比一、定义和基本使用refreactive 二、响应式原理refreactive 三、适用场景refreactive 四、注意事项refreactive Vue 3 中 ref 与 reactive 的对比 在 Vue 3 中,ref 和 reactive 都是用于创建响应式…...
centos-stream-9上安装nvidia驱动和cuda-toolkit
这里写目录标题 驱动安装1. 更新系统2. NVIDIA GPU安装检查系统是否安装了 NVIDIA GPU2.1 首先,使用以下命令更新 DNF 软件包存储库缓存:2.2 安装编译 NVIDIA 内核模块所需的依赖项和构建工具2.3 在 CentOS Stream 9 上添加官方 NVIDIA CUDA 软件包存储库…...
从 MySQL 切换到国产 YashanDB 数据库时,需要在数据库字段和应用连接方面进行适配 ,使用总结
YashanDB | 崖山数据库系统 - 崖山科技官网崖山数据库系统YashanDB是深圳计算科学研究院完全自主研发设计的新型数据库系统,融入原创理论,支持单机/主备、共享集群、分布式等多种部署方式,覆盖OLTP/HTAP/OLAP交易和分析混合负载场景ÿ…...
【学习笔记】头文件中定义函数出现重复定义报错
目录 错误复现原因解决方案inlinestatic 扩展参考 错误复现 现在有一个头文件 duplicate_define.h 和两个源文件 duplicate_define_1.cpp 和 duplicate_define_2.cpp。 两个源文件都引入了头文件 duplicate_define.h,且在各自的函数中调用了定义在头文件中的全局函…...
游戏开发中 C#、Python 和 C++ 的比较
🎬 Verdure陌矣:个人主页 🎉 个人专栏: 《C/C》 | 《转载or娱乐》 🌾 种完麦子往南走, 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️ 摘要: 那么哪种编程语言最适合游戏开发…...
linux上anaconda安装、卸载、及不同用户共享同个anaconda的操作
这里写目录标题 1、anaconda安装2、所有账号可以访问condastep1: 创建文件step2: 追加以下内容:step3: 赋予执行权限:step4: 生效方式: 3、anaconda3的卸载(1)删除安装文件夹(2)在当前终端会话中…...
利用持久变量绕过长度限制 + unicode特性绕过waf-- xyctf 出题人已疯12 复现
本文章附带TP(Thinking Process)! 黑盒查看网站不具有功能,需要审计代码 # 定义/attack路径的路由 bottle.route(/attack) def attack():# 从请求的查询参数中获取payloadpayload bottle.request.query.get(payload)# 检查payload是否存在,长度是否小于25ÿ…...
