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

java 并发编程八股-多线程篇

概括,总结,反思,对比

java 操作的线程具体是什么

在不考虑 java 最新特性,虚拟线程的情况下,指代的就是系统线程,因为在虚拟线程这个概念出来的之前,java 就要一直有一个问题(用户态和系统态线程资源切换的一个性能问题。)。所以 java 操作的就是普通的系统线程。

但是在虚拟线程出来之后,就不是这样了,java 在真实系统线程和用户之间添加了一个中间层,让用户不在直接关注系统线程。这样就减少了线程操作的性能问题,更加轻量。

使用多线程要注意的问题

首先关注的就是三要素,原子性 ,可见性,有序性,。这三点就是使用多线程要注意的问题。防止多个线程争抢出现数据不一致的问题。

  • 原子性

这里可以指定,变量操作的原子性,代码块操作的原子性。比如常见的超卖问题,原子递增,原子类等,都是要在多线程环境下让系统的执行结果符合你思考的结果。

  • 可见性

    可见性就要求变量的变更是及时的,当前线程更新完要让其他线程感知到更新。(上锁,解锁。volatile)

  • 有序性

指令的执行结果,顺序也要符合预期。(要考虑到,指令重拍,volatile 特性)

创建线程的几种方法

  • 继承 thread类

这样是最经典的方法,直接继承thread 类,new 对象。start()这种方式就是简单,缺定就是占用继承位置(java 不能多继承

  • 实现 Runnable 接口

这个也挺经典的,就是实现这个接口以及重写一个 run 方法。
在使用的时候是,new thread 对象并传递你的接口对象就行。(设计模式:装饰器-----唉,这个不对是,策略模式

  • 装饰器

装饰器,意在传递一个类型,额外增加功能(文件流 -》 文件缓冲流)

  • 策略模式

这种模式的重点是,自己传递不同的算法实现,来达到预定的效果(你实现接口,重写的那个 run 方法)

IO 一类才是装饰器

  • 使用线程池

这种方法是现在最常见的一种。

优点 就是,创建使用简单,线程也不会被回收,能够最大程度的自定义。

避免频繁的线程销毁与创建,实现线程的复用,降低性能的损耗。
**缺定:**恰恰就是自由度高,可复用性这两个缺定,所延伸的问题就是如何填写,合适的参数。

threadLocal 如和避免内存泄露(垃圾没有被回收,一直占用内存到满)。

解决方法:自定义实现(封装轮子,自己实现销毁移除操作。)

线程池

创建线程池的几种方法,为什么推荐使用。
来自 LLM:

推荐使用 ThreadPoolExecutor的原因​​:1.
​​资源可控​​ - 避免无界队列导致的内存溢出2.
​​行为明确​​ - 所有参数都可配置,行为可预测3.
​​稳定性强​​ - 适合生产环境的高并发场景4.
​​可监控性​​ - 提供丰富的状态监控方法5.
​​灵活性高​​ - 支持自定义线程工厂和拒绝策略虽然在开发测试阶段 Executors很方便,但在生产环境中必须使用 ThreadPoolExecutor来确保系统的稳定性和可靠性。

这里简单总结一下,后面有线程池篇章,再说。使用ThreadPoolExecutor的原因很多,但是也很简单。就是其他方式虽然简单,但是可控性,自定义性不高。还容易出现各种问题。(什么,无界队列,无限创建线程等问题)

如何停止线程

  • 优雅停止

简单来说就添加标记信息,通过(线程调用interrupt())这个方法来给线程打标记,来检测这个标记位置并手动,抛出异常实现优雅停止。

在沉睡中死去:这种方法看着挺暴力,其实也是添加标记。先睡眠,在打标记,这里自动抛出中断异常并停机。

使用 return:在 run 方法中判断标记信息然后停机。

  • 暴力停止

使用 stop 方法暴力停止。这个方法已经废弃,因为强制停机不知道会有什么问题。

线程的生命周期

初始状态, 线程对象创建了,但是还没有调用 start ()方法。

可运行状态,调用 start()方法后进入就绪状态。并等待 CPU 调用。

阻塞状态, 试图获取锁,等待锁释放。

等待状态,

含有等待时间的等待状态,

终止状态,执行结束或者因为异常终止。

sleap和wait 的区别

  • sleap

这个方法属于 thread 类的静态方法。可以在任意位置调用。

不会放弃当前持有的锁。但是在这期间会释放 CPU 的时间片。

超过时间(休眠结束)会自动恢复。

  • wait

这个方法属于实例,只能在同步代码块中调用。且会放弃当前持有的锁。

会让当前线程进入等待状态,暂停执行。

只能通过(notify/notifyAll)方法唤醒。

notify和notifyAll的区别

这两个都是用来唤醒被 wait 暂停的线程。宏观的区别是,notify 是唤醒一个,all 是唤醒所有。

微观的区别要看,jvm 的具体实现。有的就是随机唤醒。有点 jvm 会维护一个队列,notify 就是唤醒第一个。

不同线程之间是如何进行通信的

通信方式有很多:基本都是在维护一个先后的状态,不能同时进行。维护线程并发安全

  • volatile保证可见性

这个标识符是用来标记变量的,让变量作为全局共享变量,让多个线程之间增加可见性。就是变更之后其他线程能够立刻感知。用这样的方式来编排线程。比如:通过 volatile 标识的变量 0 代表一种状态,true 又代表一种……

volatile极限状态下有什么问题。

在内存当中,和volatile同行的内存会被频繁刷新,让一部分内存和他自己无法使用到 cpu 缓存的优势,降低访问操作的效率。另一方面,因为变量的在频繁改,频繁刷新,其他线程都针对同一块内存的频繁访问。增加一个总线的压力,导致总线风暴。降低可用性。

总结:无法充分利用,jvm 重排序(并不都是坏的,因为大部分情况下用不到 volatile),cpu 缓存。的优势。

  • synchronized,wait,notify

因为 wait,notify 只能用在实例方法中,同步代码块中。所有也要算上 synchronized。

简单举例来说,就是一个消息队列循环消费的场景(循环打印奇偶数)。

public class OddEvenPrinter {private final Object lock = new Object();private int number = 1;private final int maxNumber;public OddEvenPrinter(int maxNumber) {this.maxNumber = maxNumber;}public void printOdd() {synchronized (lock) {while (number <= maxNumber) {// 如果是偶数,就等待while (number % 2 == 0) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();return;}}// 打印奇数并增加if (number <= maxNumber) {System.out.println(Thread.currentThread().getName() + ": " + number);number++;}// 通知偶数线程lock.notifyAll();}}}public void printEven() {synchronized (lock) {while (number <= maxNumber) {// 如果是奇数,就等待while (number % 2 != 0) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();return;}}// 打印偶数并增加if (number <= maxNumber) {System.out.println(Thread.currentThread().getName() + ": " + number);number++;}// 通知奇数线程lock.notifyAll();}}}public static void main(String[] args) {OddEvenPrinter printer = new OddEvenPrinter(10);Thread oddThread = new Thread(printer::printOdd, "奇数线程");Thread evenThread = new Thread(printer::printEven, "偶数线程");oddThread.start();evenThread.start();try {oddThread.join();evenThread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("打印完成!");}
}
  • 使用CountDownLatch 方法

这个方法允许一个或者多个线程等待其他线程完成操作。最终主线程继续执行。(问题,这玩意会阻塞主线程)
这是一个实现简单 CompletableFuture

  • 使用CyclicBarrier 方法

也是一个同步辅助类,允许一组线程互相等待。知道所有线程都到到某个公共屏障点。
可以传递到达数量,以及到达后的操作

CyclicBarrier 和CountDownLatch对比

快速对比表格

特性CountDownLatchCyclicBarrier
重置能力不可重置,一次性使用可重置,循环使用
等待机制主线程等待工作线程所有线程相互等待
计数方向递减计数(countDown())递增计数(await())
使用场景主线程等待多个任务完成多个线程相互等待
构造方法CountDownLatch(int count)CyclicBarrier(int parties)
异常处理相对简单需要处理BrokenBarrierException
灵活性相对简单支持屏障动作(Runnable)

适用场景总结
CountDownLatch 适用场景:
1.
启动顺序控制 - 主线程等待所有准备工作完成

  1. 任务完成检测 - 等待多个并行任务完成
  2. 资源初始化 - 等待所有资源初始化完成后开始服务
  3. 测试协调 - 等待所有测试线程准备就绪

CyclicBarrier 适用场景:
1.
多阶段任务 - 多个线程需要同步执行多个阶段

  1. 并行计算 - 等待所有计算单元完成当前阶段
  2. 数据分片处理 - 处理完一个数据分片后等待其他分片
  3. 游戏同步 - 多个玩家需要同步进行每个回合
http://www.xdnf.cn/news/18700.html

相关文章:

  • 【iOS】内存管理及部分Runtime复习
  • Kubernetes高可用架构设计:多Master节点部署与etcd集群运维深度指南
  • centos7 安装coze
  • ZYNQ [Petalinux的运行]
  • Pytorch框架的训练测试以及优化
  • 数据结构青铜到王者第三话---ArrayList与顺序表(2)
  • 区块链技术原理(18)-以太坊共识机制
  • 哈夫曼树详解
  • 神经网络|(十五)概率论基础知识-协方差标准化和皮尔逊相关系数
  • 人机协作,温暖升级:有鹿机器人与保洁张阿姨的故事
  • 2025年06月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • Python Day 33 JavaScript BOM 与 DOM 核心笔记整合
  • Linux(从入门到精通)
  • Elasticsearch JVM调优:核心参数与关键技巧
  • 2025生成式引擎优化(GEO)技术研究报告:技术演进、行业应用与服务商能力选择指南
  • 《微服务架构下API网关流量控制Bug复盘:从熔断失效到全链路防护》
  • 解析电商本地生活竞争:从我店模式创新到生态协同的进化路径
  • 基坑监测报警系统方案:实时监测数据联动响应方式
  • Node.js特训专栏-性能优化:24.V8引擎内存管理机制
  • Python办公——爬虫百度翻译网页版(自制翻译小工具——进阶更新版)
  • 渗透测试报告编写平台 | 简化和自动化渗透测试报告的生成过程。
  • 大数据治理域——离线数据开发
  • 深度学习(二):数据集定义、PyTorch 数据集定义与使用(分板块解析)
  • leetcode 498. 对角线遍历 中等
  • (论文速读)FloVD:光流遇见视频扩散模型,开启相机控制视频生成
  • RAG实现多语言客户端的技术方案
  • Claude Code 使用及配置智能体
  • MQTT协议详解:从基础原理到工业级实践指南
  • CANopen - DCF(Device Configuration File) 介绍
  • Apache Maven 3.1.1 (eclipse luna)