Java 多线程并发
Java 多线程并发Java 并发知识库Java 线程实现 和 创建方式继承 Tread 类Thread 类本质上是实现了 Runnable 接口的一个实例代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法它将启动一个新线程并执行 run()方法publicclassMyThreadextendsThread{publicvoidrun(){System.out.println(MyThread.run());}}MyThreadmyThread1newMyThread();myThread1.start();实现 Runnable 接口如果自己的类已经 extends 另一个类就无法直接 extends Thread此时可以实现一个Runnable 接口。publicclassMyThreadextendsOtherClassimplementsRunnable{publicvoidrun(){System.out.println(MyThread.run());}//启动 MyThread需要首先实例化一个 Thread并传入自己的 MyThread 实例MyThreadmyThreadnewMyThread();ThreadthreadnewThread(myThread);thread.start();//事实上当传入一个 Runnable target 参数给 Thread 后Thread 的 run()方法就会调用target.run()publicvoidrun(){if(target!null){target.run();}}}ExecutorService、Callable、Future 有返回值线程有返回值的任务必须实现 Callable 接口类似的无返回值的任务必须 Runnable 接口。执行Callable 任务后可以获取一个 Future 的对象在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了//创建一个线程池ExecutorServicepoolExecutors.newFixedThreadPool(taskSize);// 创建多个有返回值的任务ListFuturelistnewArrayListFuture();for(inti0;itaskSize;i){CallablecnewMyCallable(i );// 执行任务并获取 Future 对象Futurefpool.submit(c);list.add(f);}// 关闭线程池pool.shutdown();// 获取所有并发任务的运行结果for(Futuref:list){// 从 Future 对象上获取任务的返回值并输出到控制台System.out.println(resf.get().toString());}}基于线程池的方式线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建不需要的时候销毁是非常浪费资源的。那么我们就可以使用缓存的策略也就是使用线程池。// 创建线程池ExecutorServicethreadPoolExecutors.newFixedThreadPool(10);while(true){threadPool.execute(newRunnable(){// 提交多个线程任务并执行Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName() is running ..);try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}}});}}4 种线程池Java 里面线程池的顶级接口是Executor但是严格意义上讲 Executor 并不是一个线程池而只是一个执行线程的工具。真正的线程池接口是ExecutorServicenewCachedThreadPool创建一个可根据需要创建新线程的线程池但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程如果线程可用。如果现有线程没有可用的则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此长时间保持空闲的线程池不会使用任何资源。newFixedThreadPool创建一个可重用固定线程数的线程池以共享的无界队列方式来运行这些线程。在任意点在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务则在有可用线程之前附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止那么一个新线程将代替它执行后续的任务如果需要。在某个线程被显式地关闭之前池中的线程将一直存在。newScheduledThreadPool创建一个线程池它可安排在给定延迟后运行命令或者定期地执行。ScheduledExecutorServicescheduledThreadPoolExecutors.newScheduledThreadPool(3);scheduledThreadPool.schedule(newRunnable(){Overridepublicvoidrun(){System.out.println(延迟三秒);}},3,TimeUnit.SECONDS);scheduledThreadPool.scheduleAtFixedRate(newRunnable(){Overridepublicvoidrun(){System.out.println(延迟 1 秒后每三秒执行一次);}},1,3,TimeUnit.SECONDS);newSingleThreadExecutorExecutors.newSingleThreadExecutor()返回一个线程池这个线程池只有一个线程,这个线程池可以在线程死后或发生异常时重新启动一个线程来替代原来的线程继续执行下去线程生命周期(状态)当线程被创建并启动以后它既不是一启动就进入了执行状态也不是一直处于执行状态。在线程的生命周期中它要经过新建(New)、就绪Runnable、运行Running、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后它不可能一直霸占着 CPU 独自运行所以 CPU 需要在多条线程之间切换于是线程状态也会多次在运行、阻塞之间切换新建状态NEW当程序使用 new 关键字创建了一个线程之后该线程就处于新建状态此时仅由 JVM 为其分配内存并初始化其成员变量的值就绪状态RUNNABLE当线程对象调用了 start()方法之后该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器等待调度运行运行状态RUNNING如果处于就绪状态的线程获得了 CPU开始执行 run()方法的线程执行体则该线程处于运行状态阻塞状态BLOCKED阻塞状态是指线程因为某种原因放弃了 cpu 使用权也即让出了 cpu timeslice暂时停止运行。直到线程进入可运行(runnable)状态才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种等待阻塞o.wait-等待对列运行(running)的线程执行 o.wait()方法JVM 会把该线程放入等待队列(waitting queue)中。同步阻塞(lock-锁池)运行(running)的线程在获取对象的同步锁时若该同步锁被别的线程占用则 JVM 会把该线程放入锁池(lock pool)中。其他阻塞(sleep/join)运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法或者发出了 I/O 请求时JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时线程重新转入可运行(runnable)状态线程死亡DEAD线程会以下面三种方式结束结束后就是死亡状态。正常结束run()或 call()方法执行完成线程正常结束。异常结束线程抛出一个未捕获的 Exception 或 Error。调用stop直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁不推荐使用。终止线程 4种方式正常运行结束程序运行结束线程自动结束使用退出标志退出线程一般 run()方法执行完线程就会正常结束然而常常有些线程是伺服线程。它们需要长时间的运行只有在外部某些条件满足的情况下才能关闭这些线程。使用一个变量来控制循环例如最直接的方法就是设一个 boolean 类型的标志并通过设置这个标志为 true 或 false 来控制 while循环是否退出。publicclassThreadSafeextendsThread{publicvolatilebooleanexitfalse;publicvoidrun(){while(!exit){//do something}}}定义了一个退出标志 exit当 exit 为 true 时while 循环退出exit 的默认值为 false.在定义 exit时使用了一个 Java 关键字 volatile这个关键字的目的是使 exit 同步也就是说在同一时刻只能由一个线程来修改 exit 的值Interrupt方法结束线程使用 interrupt()方法来中断线程有两种情况线程处于阻塞状态如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时会使线程处于阻塞状态。当调用线程的 interrupt()方法时会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常通过代码捕获该异常然后 break 跳出循环状态从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束实际上是错的 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环才能正常结束 run 方法。线程未处于阻塞状态使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时中断标志就会置 true和使用自定义的标志来控制循环是一样的道理。publicclassThreadSafeextendsThread{publicvoidrun(){while(!isInterrupted()){//非阻塞过程中通过判断中断标志来退出try{Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出}catch(InterruptedExceptione){e.printStackTrace();break;//捕获到异常之后执行 break 跳出循环}}}}stop方法终止线程线程不安全程序中可以直接使用 thread.stop()来强行终止线程但是 stop 方法是很危险的就象突然关闭计算机电源而不是按正常程序关机一样可能会产生不可预料的结果不安全主要是thread.stop()调用之后创建子线程的线程就会抛出 ThreadDeatherror 的错误并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块都是为了保护数据的一致性如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制)那么被保护数据就有可能呈现不一致性其他线程在使用这些被破坏的数据时有可能导致一些很奇怪的应用程序错误。因此并不推荐使用 stop 方法来终止线程。sleep与wait区别对于 sleep()方法我们首先要知道该方法是属于 Thread 类中的。而 wait()方法则是属于Object 类中的。sleep()方法导致了程序暂停执行指定的时间让出 cpu 该其他线程但是他的监控状态依然保持者当指定的时间到了又会自动恢复运行状态。在调用 sleep()方法的过程中线程不会释放对象锁。而当调用 wait()方法的时候线程会放弃对象锁进入等待此对象的等待锁定池只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。start与run区别start方法来启动线程真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕可以直接继续执行下面的代码。通过调用 Thread 类的 start()方法来启动一个线程 这时此线程是处于就绪状态 并没有运行。方法 run()称为线程体它包含了要执行的这个线程的内容线程就进入了运行状态开始运行 run 函数当中的代码。 Run 方法运行结束 此线程终止。然后 CPU 再调度其它线程。JAVA后台线程定义守护线程–也称“服务线程”他是后台线程它有一个特性即为用户线程 提供 公共服务在没有用户线程可服务时会自动离开。优先级守护线程的优先级比较低用于为系统中的其它对象和线程提供服务。设置通过 setDaemon(true)来设置线程为“守护线程”将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。在 Daemon 线程中产生的新线程也是 Daemon 的。线程则是 JVM 级别的以 Tomcat 为例如果你在 Web 应用中启动一个线程这个线程的生命周期并不会和 Web 应用程序保持同步。也就是说即使你停止了 Web 应用这个线程依旧是活跃的。example: 垃圾回收线程就是一个经典的守护线程当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾垃圾回收器也就无事可做所以当垃圾回收线程是 JVM 上仅剩的线程时垃圾回收线程会自动离开。它始终在低级别的状态中运行用于实时监控和管理系统中的可回收资源。生命周期守护进程Daemon是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端但是依赖于系统与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候JVM 就可以退出了如果还有一个或以上的非守护线程则 JVM 不会退出。

相关新闻

Kiran图标主题配置脚本解析:configure脚本的工作原理与自定义选项

Kiran图标主题配置脚本解析:configure脚本的工作原理与自定义选项

Kiran图标主题配置脚本解析:configure脚本的工作原理与自定义选项 【免费下载链接】kiran-icon-theme The kiran-icon-theme package contains the standard icon theme for the Kiran desktop, which provides default appearance for icons. 项目地址: https://…

2026/7/1 4:57:21阅读更多 →
【电赛/毕设高端局】DMA数据全是0?STM32H7/F7 Cache一致性灾难、DWT纳秒测速与 CMSIS-DSP 极限榨汁指南

【电赛/毕设高端局】DMA数据全是0?STM32H7/F7 Cache一致性灾难、DWT纳秒测速与 CMSIS-DSP 极限榨汁指南

前言 现在已经是 2026 年,电赛的算力军备竞赛早已进入白热化。为了跑更复杂的控制算法和信号处理,大家纷纷抛弃了 F1/F4,拿起了主频高达 400MHz 的 STM32H7 甚至更强的双核芯片。 但无数队伍在换上 H7 后的第一天就崩溃了: “为什…

2026/7/1 4:57:21阅读更多 →
N_m3u8DL-RE技术深度解析:现代流媒体下载架构实现

N_m3u8DL-RE技术深度解析:现代流媒体下载架构实现

N_m3u8DL-RE技术深度解析:现代流媒体下载架构实现 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE 在…

2026/7/1 4:52:21阅读更多 →
哑铃图:数据对比的优雅之选合集 - 数据可视化(66)

哑铃图:数据对比的优雅之选合集 - 数据可视化(66)

哑铃图是什么?哑铃图(Dumbbell Plot),有时也称为DNA图或杠铃图,是一种用于比较两个相关数据点的可视化图表。它源于人们对更有效数据比较方式的持续探索。在传统的时间序列比较中,我们通常使用两条折线&…

2026/7/1 6:07:25阅读更多 →
SAP PS模块实战:手把手教你用BAPI批量创建WBS和项目(附透明表查询技巧)

SAP PS模块实战:手把手教你用BAPI批量创建WBS和项目(附透明表查询技巧)

SAP PS模块实战:BAPI批量创建WBS与透明表查询全攻略在装备制造和能源工程这类大型项目中,手动逐个创建WBS元素就像用勺子挖运河——理论上可行,但实操中会让你怀疑人生。作为经历过数十个SAP PS模块实施的顾问,我总结出一套高效可…

2026/7/1 6:07:25阅读更多 →
北京到长白山没有直达高铁

北京到长白山没有直达高铁

先交代一下背景。我们3组家庭,都是北京海淀的,孩子从6岁到10岁不等,平时在同一个小区里玩,暑假想一起带娃出去走走。商量来商量去,最后选定了长白山哈尔滨——长白山夏季均温22℃,哈尔滨俄式风情拉满&#…

2026/7/1 6:07:25阅读更多 →
如何为小米穿戴设备创建个性化表盘:Mi-Create完整指南与实战教程

如何为小米穿戴设备创建个性化表盘:Mi-Create完整指南与实战教程

如何为小米穿戴设备创建个性化表盘:Mi-Create完整指南与实战教程 【免费下载链接】Mi-Create Unofficial watchface creator for Xiaomi wearables ~2021 and above 项目地址: https://gitcode.com/gh_mirrors/mi/Mi-Create 你是否厌倦了小米手环或智能手表上…

2026/7/1 6:07:25阅读更多 →
告别编译地狱!手把手教你用预编译的OSG库在Windows上快速搭建三维地球(C++/OpenGL)

告别编译地狱!手把手教你用预编译的OSG库在Windows上快速搭建三维地球(C++/OpenGL)

30分钟极速搭建三维地球:Windows平台OSG预编译库实战指南当C开发者初次接触三维地理可视化时,往往会被OpenSceneGraph(OSG)的编译依赖吓退。本文将带你绕过源码编译的深坑,直接使用预编译库在Visual Studio中快速构建可…

2026/7/1 6:07:25阅读更多 →
别再手动画图了!用Python脚本一键生成YOLO results.txt的PR曲线对比图(附完整代码)

别再手动画图了!用Python脚本一键生成YOLO results.txt的PR曲线对比图(附完整代码)

别再手动画图了!用Python脚本一键生成YOLO results.txt的PR曲线对比图(附完整代码)在目标检测领域的研究和工程实践中,PR曲线(Precision-Recall Curve)是评估模型性能的核心指标之一。无论是撰写学术论文还…

2026/7/1 6:02:25阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/1 4:42:14阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/7/1 5:19:01阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/1 0:01:44阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/1 0:01:44阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/1 0:01:44阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/1 0:01:44阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/1 0:01:44阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/1 0:01:44阅读更多 →