当前位置: 首页 > article >正文

Java 从入门到精通(十四):多线程入门,为什么程序一并发就开始变得“不听话”?

Java 从入门到精通十四多线程入门为什么程序一并发就开始变得“不听话”前一篇我们把 NIO 这条线讲清楚了为什么 Java 后来不满足于传统 IO为什么会引入 Path、Files、Buffer、Channel、Selector 这些更偏工程化的抽象。但当你继续往后学很快就会遇到另一个更容易让人头疼的话题多线程。很多人第一次接触多线程时都会有一种很强烈的落差感。前面学变量、分支、循环、方法、类、集合时代码基本都还符合一种“顺着往下执行”的直觉第 1 行执行完再执行第 2 行然后执行第 3 行程序逻辑像一条清晰的线但一旦进入并发世界这种直觉会迅速被打破。因为这时你要面对的不再只是“代码写没写对”而是为什么明明逻辑没问题结果却偶尔不对为什么有时候运行正常有时候结果乱掉为什么线程一多程序反而更慢为什么两个线程同时改一个变量就会出现莫名其妙的 bug为什么别人总说“线程安全”但新手很难直观理解它到底在说什么所以这篇文章不是要把并发包一次性讲完而是先把多线程最重要的入门骨架搭起来线程到底是什么它和进程是什么关系为什么程序需要并发而不是一直单线程Java 里创建线程有哪些方式start() 和 run() 到底差在哪什么叫线程安全为什么共享数据最容易出问题学多线程时初学者最容易踩哪些坑你先把这套基础认知搭稳后面再去学同步、锁、线程池、并发容器、JUC才不会一上来就被术语砸晕。一、先搞清楚什么是进程什么是线程很多教程上来就说线程是“程序执行的最小单位”这句话没错但太抽象。更直观一点的理解是1进程正在运行的一个程序实例比如你电脑上同时开着IDEA微信Chrome音乐播放器这些运行中的应用每一个都可以看作一个进程。进程有自己的资源空间比如内存文件句柄网络连接运行上下文所以进程更像是一个“独立的运行容器”。2线程进程内部真正执行任务的路径一个进程里不一定只有一条执行线。比如浏览器可以同时做这些事渲染页面加载图片执行 JavaScript处理用户点击发网络请求如果这些事情全都串行排队体验会非常差。所以一个进程内部通常会有多个线程分别负责不同工作。你可以先把它粗略理解成进程是房子线程是房子里活动的人房子提供空间和资源人真正去干活。二、为什么单线程很多时候不够用很多初学者一开始会想“一个程序顺着执行不是挺好吗为什么非要搞多线程”因为现实任务并不总适合排成一条线。1为了同时处理多个任务比如一个聊天程序可能要同时接收消息显示界面发送图片保存聊天记录如果全部都靠一条线程串着做只要其中某一步卡住其他事情就会被拖住。2为了避免界面卡死这是桌面程序和移动端里很常见的动机。比如主线程负责界面渲染如果你把一个耗时操作直接放进去读取大文件请求网络复杂计算那界面就会卡住用户会感觉“程序死了”。所以常见做法是主线程负责交互后台线程负责耗时任务3为了更好利用多核 CPU现代机器通常不是只有一个 CPU 核心。如果程序能把任务拆开并行执行就有机会把多核算力利用起来。当然这里要注意一个现实问题不是“开线程”就一定更快。如果任务本身拆不开或者线程切换成本太高线程越多反而越慢。所以多线程不是银弹它只是解决特定问题的一种手段。三、Java 程序其实从一开始就有线程很多人以为“写了多线程代码程序才有线程”。其实不是。Java 程序启动后至少就已经有一条主线程在执行 main() 方法。例如publicclassDemo{publicstaticvoidmain(String[]args){System.out.println(Thread.currentThread().getName());}}运行后通常会输出main这说明你的程序天然就在一个线程里运行。所谓“多线程”只是说除了主线程之外你又创建了新的执行路径。四、Java 创建线程的两种经典方式入门阶段最常见的是这两种继承 Thread实现 Runnable后面你会发现工程里更常用线程池和 ExecutorService但基础认知还是得从这里来。五、方式一继承 Thread 类先看最经典的写法publicclassMyThreadextendsThread{Overridepublicvoidrun(){for(inti1;i5;i){System.out.println(getName() 正在执行i);}}}然后在主方法里启动它publicclassDemo{publicstaticvoidmain(String[]args){MyThreadt1newMyThread();MyThreadt2newMyThread();t1.start();t2.start();}}你会看到输出顺序通常不是固定的。这很正常。因为两个线程是并发执行的调度顺序由 JVM 和操作系统共同决定。这种写法的优点直观好理解适合第一次接触线程时建立概念它的局限Java 只能单继承如果你的类已经继承了别的父类就不能再继承 Thread“任务逻辑”和“线程对象”耦合得太紧所以在真实开发里更推荐第二种方式。六、方式二实现 Runnable 接口publicclassMyTaskimplementsRunnable{Overridepublicvoidrun(){for(inti1;i5;i){System.out.println(Thread.currentThread().getName() 执行任务i);}}}使用时这样写publicclassDemo{publicstaticvoidmain(String[]args){MyTasktasknewMyTask();Threadt1newThread(task,线程A);Threadt2newThread(task,线程B);t1.start();t2.start();}}这里要注意一个关键变化Runnable 负责描述“要做什么事”Thread 负责把这个任务放到线程里执行这就比直接继承 Thread 更灵活。为什么 Runnable 更常用因为它把任务执行载体拆开了。这是一种更合理的设计。后面学线程池时你会更明显感受到这一点线程池管理的是线程而你提交进去的是任务。七、run() 和 start() 到底有什么区别这是多线程入门最经典、也最容易被面试反复问的一个点。很多初学者第一次写线程时会这样MyThreadtnewMyThread();t.run();然后以为“线程启动了”。其实没有。1直接调用 run()本质上只是把一个普通方法当普通方法执行。也就是说代码还是跑在当前线程里。2调用 start()才是真正告诉 JVM请启动一个新线程并在这个新线程里执行 run()。所以一定要记住run()普通方法调用start()启动新线程看一个对比publicclassDemo{publicstaticvoidmain(String[]args){ThreadtnewThread(()-{System.out.println(子线程Thread.currentThread().getName());});t.run();System.out.println(主线程Thread.currentThread().getName());}}这里很可能两行都输出 main。但如果改成t.start();那么子任务就会在另一个线程里执行。这个区别必须吃透。八、为什么线程的输出顺序经常“乱”很多人第一次运行多线程程序都会困惑“我代码明明写得很顺为什么输出不按顺序来”因为多个线程并发执行时谁先拿到 CPU 时间片谁先输出并不是你代码书写顺序决定的。例如publicclassDemo{publicstaticvoidmain(String[]args){Threadt1newThread(()-{for(inti0;i5;i){System.out.println(A-i);}});Threadt2newThread(()-{for(inti0;i5;i){System.out.println(B-i);}});t1.start();t2.start();}}输出可能是A-0A-1B-0A-2B-1B-2A-3B-3A-4B-4也可能完全是另一种顺序。这不是 bug而是并发的正常表现。所以多线程世界里一个基本认知是不要依赖“看起来刚好是这样”的执行顺序。如果逻辑必须有顺序保障那你需要显式控制而不是碰运气。九、什么叫共享数据为什么它最危险真正让多线程变复杂的不是“同时执行”本身而是多个线程同时操作同一份数据。比如这样一个例子publicclassCounter{intcount0;publicvoidincrement(){count;}}如果只有一个线程调用 increment()基本没问题。但如果多个线程同时调它就可能出错。为什么 count 不安全很多初学者会觉得“加 1 不是一个动作吗”其实从底层看它往往不是单一步骤而更像先读出 count在原值上加 1再把结果写回去如果两个线程同时做这三步就可能发生覆盖。比如线程 A 读到 5线程 B 也读到 5A 写回 6B 也写回 6结果两次加一最后却只变成了 6。这就是典型的并发问题。十、什么是线程安全你现在不用追求特别严格的定义先抓住最实用的理解如果一段代码在多个线程同时使用时结果仍然正确、不会出现数据错乱那它就是线程安全的。对应地不安全的常见信号包括结果偶尔不对同样代码重复跑每次结果不一样明明逻辑正确却会“随机出 bug”小规模测试没问题一并发就出事这也是为什么并发 bug 特别烦人。因为它们常常不是“必现”的而是“偶发”的。偶发 bug 比稳定 bug 更难查。十一、先别急着记锁先记住三类并发风险初学多线程时不要一上来就背 synchronized、Lock、volatile 一堆术语。先把问题本身认出来更重要。1竞争条件多个线程争抢同一份资源导致结果依赖执行时机。例如多个线程同时修改一个计数器。2可见性问题一个线程改了变量另一个线程未必立刻看得到。这类问题初学时比较抽象但后面学 volatile 就会更清楚。3有序性问题某些操作在底层执行时顺序未必和你表面看到的一模一样。这一点在深入并发模型时很关键不过入门阶段先知道它存在就够了。先记住并发问题不是只有“抢变量”这么简单。它至少还涉及“看不看得见”和“执行顺序会不会变”。十二、线程常见的几个基础方法先建立直觉入门阶段你不需要一口气全背完但下面这些很常见1sleep()让当前线程暂停一段时间。try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}常用于演示线程交替执行模拟耗时任务但要注意sleep() 不会释放你已经拿到的锁。2join()让一个线程等待另一个线程执行完。例如ThreadtnewThread(()-{System.out.println(子线程开始);});t.start();t.join();System.out.println(主线程继续);这表示主线程要等 t 执行结束再往下走。3currentThread()获取当前正在执行的线程对象。它对调试非常有用。System.out.println(Thread.currentThread().getName());4setName() / 线程名给线程起个名字调试时会清楚很多。比如ThreadtnewThread(task,订单处理线程);比一堆默认的 Thread-0、Thread-1 更容易看懂。十三、为什么说“线程越多不一定越快”这是一个特别值得尽早建立的观念。很多人初学并发时会天然觉得一个线程干活不如十个线程一起干十个线程不如一百个线程一起干听起来很合理但现实并不是线性增长。因为线程本身也有成本创建成本销毁成本上下文切换成本内存占用成本共享数据同步成本如果任务很小、线程很多程序可能大部分时间都浪费在线程调度上。所以多线程的目标不是“开更多线程”而是用合适的并发方式提高吞吐、响应性或资源利用率。这也是后面为什么要学线程池。线程池的核心思想本质上就是别频繁手搓线程统一管理更合理。十四、初学多线程最容易踩的 6 个坑1把 run() 当成启动线程的方法这是最经典的坑。再次强调run() 只是普通方法start() 才会开启新线程2看到输出顺序乱就以为程序错了并发本来就不保证自然顺序。不能拿单线程直觉去要求多线程。3多个线程共享一个变量却没意识到会出问题这会直接导致数据错乱。4以为加了线程性能就一定提升实际还要看任务是否适合并行线程切换是否过多有没有锁竞争CPU 是否真能吃满5用 sleep() 解决同步问题很多新手喜欢这样写“我先 sleep(100)另一个线程应该就跑完了吧。”这非常不稳。sleep() 只能“拖时间”不能保证逻辑同步正确。真正需要顺序控制时应考虑 join()、锁、条件变量等更可靠手段。6一上来就猛学高级并发包基础却没稳如果你连线程是什么共享数据为什么危险start() / run() 区别为什么结果会乱序这些基础都没吃透直接上 AQS、线程池源码、CAS通常只会越学越乱。十五、给你一个更稳的学习顺序如果你现在刚接触多线程我建议按这个顺序学第一步先建立线程直觉先彻底理解进程和线程的关系为什么要并发如何创建线程start() 和 run() 的区别第二步再理解共享数据问题重点搞清楚什么是线程安全为什么 count 会出错什么叫竞争条件第三步再学同步手段包括synchronized锁对象同步代码块volatile原子类第四步最后进入工程化并发包括线程池并发容器FutureCompletableFutureJUC 工具类这样推进比一上来就硬啃高级并发原理稳得多。十六、最后总结多线程难不是因为 API 多而是因为“时间”进来了如果说前面学集合、泛型、异常、IO本质上还是在处理“单条执行线里的正确性”那么多线程带来的最大变化是程序不再只和数据打交道而是开始和“时间顺序”打交道。也正因为这样问题会突然变复杂同样的代码先后顺序不同结果就可能不同多个线程同时执行输出天然可能交错共享变量一旦出现逻辑就不再只是“写对语法”那么简单所以你这篇真正要带走的不是会写两个线程打印数字而是这几个核心认识1线程是进程内部的执行路径Java 程序启动后本身就有主线程。2多线程是为了解决并发任务、响应性和资源利用率问题但它不是越多越好。3创建线程的经典方式有两种继承 Thread实现 Runnable工程里通常更偏向 Runnable 这种任务与线程分离的方式。4start() 和 run() 必须分清这是多线程入门的第一道门槛。5真正危险的地方在共享数据线程安全问题本质上多数都和共享状态有关。从这里开始Java 学习就正式进入一个更像“真实工程”的阶段了。因为并发不是可选装饰而是现代程序几乎绕不开的一块地基。下一篇最自然的继续方向就是线程同步与 synchronized为什么多个线程改同一个变量时结果总会乱

相关文章:

Java 从入门到精通(十四):多线程入门,为什么程序一并发就开始变得“不听话”?

Java 从入门到精通(十四):多线程入门,为什么程序一并发就开始变得“不听话”? 前一篇我们把 NIO 这条线讲清楚了:为什么 Java 后来不满足于传统 IO,为什么会引入 Path、Files、Buffer、Channel、…...

Sunshine开源游戏串流服务器:免费搭建跨平台低延迟游戏共享系统

Sunshine开源游戏串流服务器:免费搭建跨平台低延迟游戏共享系统 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款开源免费的自托管游戏串流服务器&#x…...

智能网速在线测试网站源码_宽带网速检测

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示一、详细介绍 智能网速在线测试网站源码_宽带网速检测 功能强大的PHP测速网页源码,旨在帮助用户快速、准确地测试网络速度。该源码可以直接部署在静态存储服务上,无需复杂的服务器配置&#xf…...

小语言模型基础:适合轻量化场景的 AI

文章目录小语言模型基础:适合轻量化场景的 AI一、啥是小语言模型?说白了就是"轻量级选手"二、2025-2026年SLM爆发:各大厂都在卷啥?2.1 微软Phi-4:数据质量党の胜利2.2 谷歌Gemma 3n:多模态小钢炮…...

主题巴巴主题源码 合辑打包下载+主题巴巴SEO插件 _ WordPress主题模版

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示一、详细介绍 主题巴巴WordPress主题合辑打包下载,包含博客一号、博客二号、博客X、门户一号、门户手机版、图片一号、杂志一号、自媒体一号、自媒体二号和主题巴巴SEO插件。 主题巴巴WordPress主题合辑打…...

小龙虾 openclaw + 财务管理系统,自动化的财务管家来了~

一张表格管理自己的财务情况,对于每个超级个体来讲,都建议把上次分享的《人生 CEO 财务管理系统》带回家。在没有正式上线应用市场之前,已经 200 多人使用过了。特别适合想做一人公司或者超级个体的人。你不理财,财不理你。并且站…...

C加加面向对象的知识点

C面向对象1.什么是面向对象?面向对象有哪些特性?2. C面向对象编程?3. 重载,重写,隐藏的区别是什么?4. C的多态是什么?怎么通过虚函数实现?5. C函数对象是什么?跟普通函数…...

HC-SR501人体感应模块的5个隐藏功能:90%的人不知道的调节技巧

HC-SR501人体感应模块的5个隐藏功能:90%的人不知道的调节技巧 当你以为HC-SR501只是个简单的人体感应开关时,它其实藏着工程师们精心设计的可编程特性。这些隐藏在电位器旋钮和跳线帽下的功能,能让模块在智能家居、安防系统中表现更精准。下面…...

使用OpenClaw的Skills对接本地系统靶

1. 流图:数据的河流 如果把传统的堆叠面积图想象成一块块整齐堆叠的积木,那么流图就像一条蜿蜒流淌的河流,河道的宽窄变化自然流畅,波峰波谷过渡平滑。 它特别适合展示多个类别数据随时间的变化趋势,尤其是当你想强调整…...

让桌面随光而动:动态壁纸的终极解决方案

让桌面随光而动:动态壁纸的终极解决方案 【免费下载链接】dynamic-wallpaper A simple bash script to set wallpapers according to current time, using cron job scheduler. 项目地址: https://gitcode.com/gh_mirrors/dy/dynamic-wallpaper 厌倦了单调乏…...

Wan2.2-I2V-A14B开发利器:在IDEA中配置远程Python解释器进行模型调试

Wan2.2-I2V-A14B开发利器:在IDEA中配置远程Python解释器进行模型调试 1. 为什么需要远程Python解释器 当开发Wan2.2-I2V-A14B这类AI模型项目时,我们经常遇到一个典型问题:本地机器性能不足,而模型运行需要服务器级别的GPU资源。…...

Python高效处理多层嵌套文件夹中的批量NC文件——以海洋数据为例

1. 海洋数据处理中的多层嵌套文件夹挑战 第一次处理海洋数据时,我被那些层层嵌套的文件夹搞得头晕眼花。想象一下这样的场景:你的硬盘里有个名为"Argo_Data"的文件夹,打开后看到"太平洋"、"大西洋"、"印度…...

大模型到底是啥?运维人分钟搞懂(不用数学)皆

1. 流图:数据的河流 如果把传统的堆叠面积图想象成一块块整齐堆叠的积木,那么流图就像一条蜿蜒流淌的河流,河道的宽窄变化自然流畅,波峰波谷过渡平滑。 它特别适合展示多个类别数据随时间的变化趋势,尤其是当你想强调整…...

Kali Linux抓包工具Burp Suite社区版安装教程(简易上手版)

前言由于本人前段时间不小心删掉了Kali Linux上的Burp Suite社区版,后面需要用的时候,在网上参考了很多文章,均要下载和后续配置。我尝试了不少方法,不仅没有安装好,还得到了两个结论,那就是耗时和耗力。后…...

微信小程序实战:打造优雅的互动消息列表(评论 / 点赞 / 关注

在绿植类小程序开发中,互动消息模块(评论、点赞、关注)是提升用户粘性的核心场景。本文将基于实际开发场景,分享如何快速实现风格统一、交互友好的评论详情、点赞详情、关注详情列表页面,包含完整的代码实现和样式设计…...

别再死记硬背C#语法了!用5个机器视觉小例子带你快速上手(Visual Studio 2022版)

用5个机器视觉小项目玩转C#语法(Visual Studio 2022实战) 刚接触C#的开发者常陷入语法记忆的泥潭,而机器视觉领域恰好需要快速验证想法的能力。本文将带你用Visual Studio 2022创建五个渐进式图像处理项目,在解决实际问题的过程中…...

**反编译防护新思路:基于混淆+加密的C++程序加固实战**在软件安全领域,**反编译防护**始终是开发者绕不开

反编译防护新思路:基于混淆加密的C程序加固实战 在软件安全领域,反编译防护始终是开发者绕不开的核心议题。尤其是对于c这类静态编译语言,虽然相比Java或Python难以直接反编译出源码,但其二进制文件仍可通过IDA Pro、Ghidra等工具…...

打通智能体孤岛:用 AgentRun 构建生产级 AA 多 Agent 管理协作系统僦

起因是我想在搞一些操作windows进程的事情时,老是需要右键以管理员身份运行,感觉很麻烦。就研究了一下怎么提权,顺手瞄了一眼Windows下用户态权限分配,然后也是感谢《深入解析Windows操作系统》这本书给我偷令牌的灵感吧&#xff…...

nanobot 源码解析(五):Skills 系统——让 AI 秒变专家敢

一、中间件是啥?咱用“餐厅”打个比方 想象一下,你的FastAPI应用是个高级餐厅。 ?? 顾客(客户端请求)来到门口。- 迎宾(CORS中间件):先看你是不是从允许的街区(域名)来…...

多模型场景下的成本治理指标体系幢

为 HagiCode 添加 GitHub Pages 自动部署支持 本项目早期代号为 PCode,现已正式更名为 HagiCode。本文记录了如何为项目引入自动化静态站点部署能力,让内容发布像喝水一样简单。 背景/引言 在 HagiCode 的开发过程中,我们遇到了一个很现实的问…...

多线程UI异常捕获实战 --- 解决Invoke与BeginInvoke的窗口句柄陷阱

1. 多线程UI编程的窗口句柄陷阱 刚接触Windows Forms多线程开发时,我经常遇到一个让人抓狂的错误:"在创建窗口句柄之前,不能在控件上调用Invoke或BeginInvoke"。这个错误就像个幽灵,有时候程序运行几天都不出现&#xf…...

企业官网设计那个最好?怎么才能融入品牌文化的视觉设计与前端落地

企业官网设计:如何通过视觉设计与前端技术深度融入品牌文化 企业官网设计不仅是信息窗口,更是品牌文化的立体化载体。优秀的官网设计需实现美学表达、用户体验与品牌内核的三维统一,本文将系统解析设计策略与落地路径。 推荐选择https://ww…...

【Linux命令饲养指南】01-Linux命令三巨头:pwd、cd、ls,看完这篇你就会了!(附带目录结构大揭秘)

hello 各位老铁,又是学习新知识的时候啦😊 咱们刚接触Linux,看着黑乎乎的终端一脸懵?别慌,今天我们用大白话去搞定最常用的命令和Linux的目录结构。本文为个人原创学习整理,AI 仅辅助排版与润色一、Linux的…...

运动声源的到达结构仿真

概要 运动声源的到达结构仿真中,由于声传播速度远高于声源运动速度,而且声源辐射出声波后,介质的振子传递声波几乎不受声源影响,因此可以将根据每个时间帧的声源位置,使用bellhop计算到达结构,数字离散采样…...

.Acwing基础课第题-简单-区间和胰

在AI辅助开发的语境下,Skill就是一个包含了领域知识、最佳实践、代码模板的知识包。 以"DAO层CRUD生成"为例,一个Skill包含: /mnt/skills/dao-crud/ ├── SKILL.md # 使用说明 │ ├── 何时使用这个Skill │ …...

VRRP实验练习

要求LSW1配置<Huawei> <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sys sw1 [sw1]v [sw1]vlan b [sw1]vlan batch 10 20 Info: This operation may take a few seconds. Please wait for a moment...done. [sw1]interface g 0/0/1 [sw…...

如何在macOS上快速定制个性化光标:Mousecape完全指南

如何在macOS上快速定制个性化光标&#xff1a;Mousecape完全指南 【免费下载链接】Mousecape Cursor Manager for OSX 项目地址: https://gitcode.com/gh_mirrors/mo/Mousecape 厌倦了macOS默认的单调鼠标指针&#xff1f;想要让光标更有个性、更符合你的审美&#xff1…...

【java工程师快速上手go】二.Go进阶特性

目录 写在前面 一、面向对象编程 1.1 结构体&#xff1a;Go的"类" 1.2 匿名字段与嵌入 1.3 结构体的组合优势 1.4 接口&#xff1a;鸭子类型的魅力 1.5 空接口与类型断言 1.6 接口组合 1.7 封装&#xff1a;大小写可见性 二、并发编程核心 2.1 Goroutine&…...

回调地狱+Promise+ES6

JavaScript 异步机制与异步编程终极指南 &#xff08;完整梳理&#xff1a;回调地狱成因 → 事件循环原理 → Promise/async/await 实战 → axios 封装&#xff09; 一、异步编程核心背景&#xff1a;为什么异步是必然&#xff1f; 1. JS 单线程本质&#xff08;异步的根源&…...

如何在Windows系统下完全解锁MacBook Pro Touch Bar:终极解决方案指南

如何在Windows系统下完全解锁MacBook Pro Touch Bar&#xff1a;终极解决方案指南 【免费下载链接】DFRDisplayKm Windows infrastructure support for Apple DFR (Touch Bar) 项目地址: https://gitcode.com/gh_mirrors/df/DFRDisplayKm 还在为Windows系统下Touch Bar只…...