带你理解JavaEE多线程基础(上)
1、了解线程1.1 线程概念1. 进程操作系统资源分配的最小单位。打开微信、浏览器、IDEA、JVM每一个都是独立进程进程拥有独立内存空间、文件句柄、CPU 时间片、堆内存进程之间完全隔离进程切换开销极大操作系统要保存整套上下文2. 线程进程内部独立执行的最小调度单位也叫轻量级进程。一个进程至少包含 1 个主线程同一个进程内所有线程共享进程的堆内存、方法区、文件资源只私有自己的栈内存CPU 只调度线程不直接调度进程线程切换成本远低于进程一句话口诀进程是房子线程是房子里干活的人房子资源所有人共用每个人各自有自己的工作台私有栈。1.2 创建线程方法1MyThread 是Demo1 的非静态内部类继承 Thread 重写 runrun 为线程执行体。非静态内部类依赖外部类实例main静态方法里直接new MyThread()编译报错。启动线程必须调用start()自动执行 run直接调用 run 只是普通方法。class Demo1 { class MyThread extends Thread{ Override public void run(){ System.out.println(hello my thread); } } } public static void main(String[] args) { MyThread myThreadnew MyThread(); myThread.start(); } }方法2采用实现Runnable接口的方式定义线程任务MyThread重写run()放置线程执行代码。MyThread加static成为静态内部类可在静态main方法直接实例化。将Runnable任务对象传入Thread构造器调用start()开启新线程执行run方法。该方式规避单继承限制任务与线程解耦开发更常用。package practice.thread; public class Demo2 { static class MyThread implements Runnable{ Override public void run(){ System.out.println(hello my thread); } } public static void main(String[] args) { Thread threadnew Thread(new MyThread()); thread.start(); } }方法3重要使用Lambda表达式简化Runnable接口省去单独创建类直接填写线程执行任务。把Lambda任务传入Thread构造方法创建线程对象调用start()启动子线程打印一句话后线程结束。属于实现Runnable方式的极简写法代码最简洁日常开发高频使用。public class Demo3 { public static void main(String[] args) { Thread threadnew Thread(()-{ System.out.println(hello my thread); }); thread.start(); } }2、Thread类及其方法2.1 常见构造方法构造方法说明Thread()创建线程对象使用默认名称Thread(Runnable target)传入 Runnable 任务创建线程对象Thread(String name)创建线程对象并自定义线程名称Thread(Runnable target, String name)传入 Runnable 任务同时自定义线程名称2.2 常见属性属性获取方法简要说明IDgetId()获取线程唯一标识 ID不可修改名称getName()获取线程名可通过setName()修改状态getState()返回线程 6 种生命周期状态NEW/RUNNABLE 等优先级getPriority()获取线程优先级1~10影响 OS 调度概率是否后台线程isDaemon()判断是否为守护线程主线程退出守护线程自动终止是否存活isAlive()判断线程是否处于运行 / 未消亡状态是否被中断isInterrupted()检测线程中断标记不清除标记2.3 启动线程执行线程的start方法是调用系统API真正在操作系统内创建一个线程这个新线程会以run作为入口方法执行run里的逻辑→run方法不显式调用创建新线程自动执行的注如果是执行run方法不创建新线程2.4 中断线程方法完整说明public void interrupt()给目标线程打上中断标记若线程处于sleep()/wait()/join()阻塞状态直接抛出InterruptedException并自动清除中断标记public static boolean interrupted()静态方法检测当前执行线程的中断标记检测后立即清空标记位public boolean isInterrupted()实例方法检测调用该方法的线程对象的中断标记只查询、不会清除标记位public class Demo6 { public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ //currentThread()是在start方法之后执行的 //并且在t线程中执行返回t线程对象 while (!Thread.currentThread().isInterrupted()) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } }); t.start(); Thread.sleep(2000); t.interrupt(); System.out.println(线程中断); } }结果注意任务内部无法直接访问外部变量t做通用判断Thread.currentThread()自动获取正在执行这段代码的当前线程通用性强同一个任务可以绑定任意线程运行。降低代码耦合线程执行逻辑不依赖外部线程引用是线程内部检测自身中断标记的标准规范写法。如果直接写t.isInterrupted()语法能跑但任务被写死只能给t线程使用无法复用。2.5 等待线程join() 作用让当前线程阻塞插队等待另一个线程执行完成实现线程执行顺序控制。方法说明public void join()无限阻塞直到调用该方法的线程执行完毕当前线程才继续往下运行public void join(long millis)限时等待最多阻塞millis毫秒超时无论目标线程是否结束当前线程都会恢复执行public void join(long millis, int nanos)高精度限时等待毫秒 纳秒组合设置等待时长极少使用package practice.thread; public class Demo4 { public static void main(String[] args) throws InterruptedException{ Thread threadnew Thread(()-{ for(int i0;i10;i){ System.out.println(hello my threadi); } }); thread.start(); // 等待线程执行3秒 thread.join(3000); for(int i0;i10;i){ System.out.println(hello maini); } } }结果不加Join vs 加Join2.6 获取当前线程引用返回当前线程对象的引用public class Demo5 { public static void main(String[] args) { Thread threadThread.currentThread(); thread.setName(main thread); System.out.println(thread.getName()); } }2.7 休眠线程我们熟悉的sleeppublic class Demo6 { public static void main(String[] args) { Thread tnew Thread(()-{ long startTimeSystem.currentTimeMillis(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } long endTimeSystem.currentTimeMillis(); System.out.println(hello my thread (endTime-startTime)s); }); t.start(); } }结果2.8 前后台线程及区别1. 前台线程默认所有新建的线程都是前台JVM 退出规则只要任意一个前台线程还在运行JVM 进程就不会终止。必须所有前台线程全部执行完毕JVM 才会正常关闭。2. 守护线程后台线程setDaemon(true) 设置当所有前台线程全部执行完毕无论守护线程有没有跑完JVM 直接强制退出守护线程被立刻粗暴终止。public class Demo4 { public static void main(String[] args) { Thread t new Thread(()-{ while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } },我的线程); System.out.println(是否是后台线程 t.isDaemon()); t.setDaemon(true);// 设置为后台线程 System.out.println(是否设置为后台线程 t.isDaemon()); System.out.println(已设置为后台线程); t.start(); for (int i 0; i 10; i) { System.out.println(hello main); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }结果原本线程t是无限循环将其设置为后台线程后线程t随着主线程结束而结束3、线程的状态3.1 线程状态情况状态简写核心说明NEW新建线程对象已创建未调用start()未启动RUNNABLE就绪 / 运行正在 CPU 执行或就绪排队争抢 CPU 时间片BLOCKED阻塞竞争synchronized同步锁失败阻塞排队等锁WAITING无限等待无期限挂起需其他线程主动唤醒才能恢复TIMED_WAITING限时等待设置等待时长超时自动苏醒也可提前唤醒TERMINATED终止run()执行完毕或异常退出线程生命周期结束3.2 状态的转移简单示例public class Demo7 { public static void main(String[] args) { Thread tnew Thread(()-{ System.out.println(hello my thread); }); System.out.println(t.getState()); t.start(); System.out.println(t.getState()); } }结果4、多线程的安全问题4.1 不安全的情况public class Demo9 { public static int count 0; public static void main(String[] args) throws InterruptedException { Thread t1new Thread(()-{ for(int i0;i50000;i){ count; } }); Thread t2new Thread(()-{ for(int i0;i50000;i){ count; } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); } }结果按照预期来讲结果应该是100000但是为什么是小于100000呢因为count不是原子操作其指令可拆解成1. 读取 count 当前值load 2. 执行数值 1 运算add 3. 把新值写回 count 变量store这三步中间线程可以被 CPU 调度打断就会发生覆盖丢失。假设 count 10 t1 读到 count10正要 1 CPU 切换到 t2t2 也读到 count10 t1 计算得到 11写回 count11 t2 计算得到 11写回 count11循环 5 万次大量这种冲突叠加最终结果必然 100000。解决方法count外加锁4.2 不安全的原因1. 线程调度是随机的操作系统采用抢占式调度线程获取 CPU 执行权、被挂起的时机完全随机不可预测。原本单线程串行执行的代码在多线程下会被任意切割、穿插执行所有并发安全问题的底层根源都来源于此。编写并发代码时必须兼容所有调度时序才能保证最终结果准确。2. 多个线程同时修改一个变量多个线程同时读写堆中静态变量、成员变量等公共资源共享变量仅有一份存储在主内存中直接产生数据竞争。若线程仅操作各自的局部私有变量不存在资源争抢天然不会发生线程安全问题。3. 修改操作不是原子的原子操作指不可被 CPU 中断、一次性执行完成的指令。count 10 单纯赋值属于原子操作count 自增是复合操作底层拆分为三步读取变量值 → 数值自增运算 → 结果写回变量。在线程切换时极易被打断出现两次自增只生效一次的丢失更新问题最终统计值小于预期。4. 内存可见性1JMM 内存模型基础Java 内存模型JMM为屏蔽不同 CPU、操作系统缓存架构差异做了两层抽象主内存对应 JVM 堆存放所有线程共享变量访问速度慢工作内存线程私有抽象空间底层为 CPU 寄存器 L1/L2/L3 高速缓存读写速度极快。线程不会直接操作主内存会先将变量拷贝到私有缓存运算再同步回主内存。2可见性异常产生过程JIT 即时编译器遵循as-if-serial规则仅保证单线程运行结果正确会对高频只读代码做性能优化。以空自旋while (flag 0)为例JIT 判定该循环只读取不修改flag仅第一次从主内存加载值存入 CPU 缓存后续循环直接复用缓存旧数据不再访问主内存。当t2修改flag并更新至主内存后t1依旧读取过期缓存值导致无限死循环无法退出。package Thread; import java.util.Scanner; public class Demo15 { // 未加volatile触发可见性问题 public static int flag 0; public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { while (flag 0) { // 空循环极易被JIT优化 } System.out.println(t1 结束); }); Thread t2 new Thread(() - { Scanner sc new Scanner(System.in); System.out.println(请输入flag的值); flag sc.nextInt(); System.out.println(t2 结束); }); t1.start(); t2.start(); t1.join(); t2.join(); } }3volatile 解决可见性及局限性1核心作用禁止 JIT 对变量做本地缓存优化强制每次读写都直接操作主内存通过内存屏障禁止指令重排序保障执行有序性。2重要局限volatile只保证可见性、有序性不具备原子性无法解决count这类复合运算并发问题仅适用于单线程写入、多线程读取的场景多线程同时修改共享变量必须使用synchronized或Lock锁。4volatile 与 synchronized 简要对比volatile轻量级无锁解决可见性与有序性不保证原子性synchronized重量级互斥锁同时保证原子性、可见性、有序性会阻塞竞争线程。5. 指令重排序1重排序定义编译器、CPU 为提升执行效率在单线程运行结果不变的前提下打乱代码原有书写顺序重新编排指令属于正常性能优化。单线程环境无影响多线程并发时会破坏业务逻辑。2经典风险案例对象初始化重排序对象创建逻辑理想有序三步分配堆内存空间执行构造方法完成对象初始化将内存地址赋值给引用变量指令重排序可能打乱为分配内存 → 赋值引用地址 → 执行构造方法初始化。其他线程若拿到未初始化完毕的对象引用会触发空指针、成员变量默认值异常等严重 bug。3约束方案volatile添加内存屏障禁止变量读写前后指令重排synchronized同步代码块锁保证临界区指令串行执行规避重排序干扰。4.3 解决这个问题5、synchronized关键字5.1 该关键字的特性1. 互斥synchronized会起到互斥效果,某个线程执⾏到某个对象的synchronized中时,其他线程如果也执⾏ 到同⼀个对象synchronized就会阻塞等待进⼊synchronized修饰的代码块,相当于加锁退出synchronized修饰的代码块,相当于解锁2. 可重入同一个线程已经拿到了某个对象的锁之后可以再次重复获取这把锁不会自己阻塞自己这就是锁的可重入机制。5.2 简要案例比如前面内容涉及的count数小于100000的情况可以通过加锁解决public class Demo12 { static class Counter{ private static int count 0; Object lockernew Object(); public void add(){ synchronized(locker){ count; } } } public static void main(String[] args) throws InterruptedException { Counter counternew Counter(); Thread t1new Thread(()-{ for(int i0;i50000;i){ counter.add(); } }); Thread t2new Thread(()-{ for(int i0;i50000;i){ counter.add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.count); } }结果

相关新闻

AI编程工程化:Rule系统提升代码质量与协作效率

AI编程工程化:Rule系统提升代码质量与协作效率

1. 为什么AI编程需要工程化管理 在2023年GPT-4发布后,AI编程助手的使用量呈现爆发式增长。根据GitHub官方统计,使用Copilot的开发者平均每天会触发20-50次代码建议,但其中仅有30%的建议会被直接采纳。这个数据揭示了一个关键问题:…

2026/10/12 15:42:14 阅读更多 →
AI编程助手架构对比:从Codex、Claude Code到Pi Agent的深度解析

AI编程助手架构对比:从Codex、Claude Code到Pi Agent的深度解析

1. 项目概述:一次对AI编程助手核心架构的深度探秘最近在AI编程工具的圈子里,几个名字被反复提及:OpenAI的Codex、Anthropic的Claude Code,以及一个相对神秘但讨论度渐高的“Pi”。作为一名长期混迹于开发者社区、热衷于折腾各种效…

2026/10/13 17:05:50 阅读更多 →
GLM-5.3:当模型「学会」了编程,它「顺带」学会了找漏洞-龍德明宇

GLM-5.3:当模型「学会」了编程,它「顺带」学会了找漏洞-龍德明宇

GLM-5.3:当模型「学会」了编程,它「顺带」学会了找漏洞 作者:龍德明宇 8月14日新发布的GLM-5.3,先看这次发布的官方标题:GLM-5.3: Frontier Coding with Emergent Cyber Capabilities。 关键词是那个 Emergent&…

2026/10/12 5:10:50 阅读更多 →

最新新闻

相关系数与决定系数:别再混淆,一文讲透模型评估核心指标

相关系数与决定系数:别再混淆,一文讲透模型评估核心指标

1. 从一个让人困惑的结论说起:相关系数高,模型就一定准吗很多人第一次接触统计建模时,都会经历这样一个场景:辛辛苦苦跑完一组数据,软件输出一个相关系数0.85,心里一阵激动,觉得变量之间的关系已…

2026/10/17 11:36:30 阅读更多 →
Agent编排实战:从单Agent瓶颈到多Agent协作的工程化指南

Agent编排实战:从单Agent瓶颈到多Agent协作的工程化指南

这两年做大模型应用,我越来越觉得,单Agent已经不太够用了。早期的时候,一个Agent塞一个超长Prompt,让它一口气完成“收集资料-分析-写报告-校对-排版”,结果经常是前面步骤一错,后面全崩,而且跑…

2026/10/17 11:36:30 阅读更多 →
Tableau合成桑吉图的底层逻辑与三大支柱字段构建

Tableau合成桑吉图的底层逻辑与三大支柱字段构建

1. 桑吉图不是“画出来的”,而是“搭出来的”——Tableau里最常被误解的可视化逻辑很多人第一次在Tableau里尝试做桑吉图(Sankey Diagram),都会下意识打开“标记”面板,拖入维度、度量,选中“线”或“路径”…

2026/10/17 11:36:30 阅读更多 →
自动驾驶感知中的PCA与LDA:安全驱动的维度约简实战指南

自动驾驶感知中的PCA与LDA:安全驱动的维度约简实战指南

1. 为什么在自动驾驶感知系统里,PCA和LDA不是“选修课”,而是必过门槛?你刚接手一个车载多传感器融合项目,激光雷达点云原始维度是20486(x/y/z/intensity/ring/timestamp),摄像头输出的ResNet-5…

2026/10/17 11:36:30 阅读更多 →
FastICA原理与sklearn实战:从盲源分离到工程落地

FastICA原理与sklearn实战:从盲源分离到工程落地

1. 这不是调个包那么简单:FastICA在sklearn里到底在解决什么问题你打开sklearn文档,看到sklearn.decomposition.FastICA这个类,第一反应可能是:“哦,独立成分分析,和PCA差不多吧?填个n_componen…

2026/10/17 11:36:30 阅读更多 →
外卖霸王餐小程序源码实战:CPS返利系统从部署到二次开发

外卖霸王餐小程序源码实战:CPS返利系统从部署到二次开发

简介:这份资源是面向本地外卖商家与小程序开发者的霸王餐系统源码,围绕美团、饿了么等主流外卖场景,提供商家入驻、霸王餐发布、优惠券设置与用户评价反馈等核心模块,帮助商家以免费体验活动吸引新客、沉淀口碑,也为想…

2026/10/17 11:35:29 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →