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

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.dtcms.com/a/350622.html

相关文章:

  • 【已解决】统信UOS安装后没有有线网络,缺少Chengdu Haiguang IC Design Co., Ltd. 10 Gb Ethernet网卡驱动
  • 支付宝直连商户,自动处理支付交易投诉,支持多支付宝应用
  • 【VS2022】背景设置详细教程(背景透明)
  • AI 时代“驯导师”职业发展方向探究
  • 用AI生成的一个BadgerDB的管理工具
  • 深入剖析Hugging Face Transformers中的KV Cache
  • Element plus日期选择器从今天开始、时间跨度为3天
  • 【Android 16】Android W 的冻结机制框架层分析
  • 茶艺实训室建设方案:打造沉浸式茶文化教学空间
  • SAVITECH盛微先进SAVIAUDIO音频解码芯片方案与应用
  • Chromium 源码中的单例管理:LazyInstance 与 NoDestructor 的深入解析与实战对比
  • vscode(MSVC)进行c++开发的时,在debug时查看一个eigen数组内部的数值
  • uniapp安卓真机调试问题解决总结
  • redis----list详解
  • C# 相机内存复用(减少图像采集耗时)以及行数复用
  • 自定义树形构造器
  • python项目实战 3D宠物狗
  • 关于传统的JavaWeb(Servlet+Mybatis)项目部署Tomcat后的跨域问题解决方案
  • MM-2025 | 北航双无人机协作助力视觉语言导航!AeroDuo:基于空中双机系统的无人机视觉语言导航
  • 简述mysql中索引类型有哪些,以及对数据库的性能的影响?
  • JBL音响代理——河北正娱科技的声学精品工程
  • 网络编程-HTTP
  • 插曲 - 为什么光速不变
  • 【代码】洛谷P3391 【模板】文艺平衡树(FHQ Treap)
  • 低质量视频变高清AI:告别模糊,重现清晰画质
  • chrome插件开发(二)
  • vue家教预约平台设计与实现(代码+数据库+LW)
  • 驱动-热插拔-Netlink广播监听内核状态
  • HarmonyOS实战(DevEco AI篇)—CodeGenie + DeepSeek构建鸿蒙开发的超级外挂工作流
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十九)子窗口