Java多线程详解(5)

一、JUC

JUC 是 java.util.concurrent 包的缩写,是 Java 并发编程的核心工具包。它从 JDK 1.5 开始引入,为解决多线程并发问题提供了强大的组件。

1.1、JUC核心架构

java 复制代码
java.util.concurrent
├── atomic      // 原子类
├── locks       // 锁框架
├── collections // 并发集合
├── executors   // 线程池框架
└── tools       // 同步工具

1.2、原子类

核心原理:CAS

java 复制代码
// CAS 伪代码
boolean compareAndSwap(V, A, B):
    if (V == A):
        V = B
        return true
    return false

原子类分类

使用示例:

java 复制代码
// AtomicInteger 基本用法
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();  // ++i
counter.getAndIncrement();  // i++

// LongAdder - 适合高并发写多读少的场景
LongAdder adder = new LongAdder();
adder.increment();
long sum = adder.sum();

// 解决 ABA 问题 - 带版本号的原子引用
AtomicStampedReference<String> ref = 
    new AtomicStampedReference<>("A", 0);

1.3、锁框架

ReentrantLock定义

可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全。

ReentrantLock的用法:

  • lock():加锁,如果获取不到锁就死等。
  • trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。
  • unlock():解锁

ReentrantLock和synchronized的区别

  • synchronized 是一个关键字,是JVM内部实现的(大概率是基于C++实现),ReentrantLock是标准库的一个类,在JVM外实现的(基于Java实现)。

  • synchronized 使用时不需要手动释放锁。ReentrantLock使用时需要手动释放。使用起来更灵活,但是也容易遗漏unlock。

  • synchronized 在申请锁失败时,会死等。 ReentrantLock可以通过trylock的方式等待一段时间就放弃。

  • synchronized 是非公平锁,ReentrantLock默认是非公平锁.,可以通过构造方法传入一个true开启公平锁模式。

  • 更强大的唤醒机制.。synchronized是通过Object的wait/notify实现等待-唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock 搭配Condition类实现等待-唤醒,可以更精确控制唤醒某个指定的线程.

如何选择使用哪个锁?

  • 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便。

  • 锁竞争激烈的时候,使用ReentrantLock,搭配trylock更灵活控制加锁的行为,而不是死等。

  • 如果需要使用公平锁,使用ReentrantLock。

1.4、线程池

核心接口

java 复制代码
Executor (顶层接口)
├── ExecutorService (核心接口)
│   ├── ThreadPoolExecutor   // 最常用
│   └── ScheduledExecutorService
│       └── ScheduledThreadPoolExecutor
└── ForkJoinPool (分治框架)

ThreadPoolExecutor 七大参数

java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,      // 核心线程数
    int maximumPoolSize,   // 最大线程数
    long keepAliveTime,    // 空闲线程存活时间
    TimeUnit unit,         // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,        // 线程工厂
    RejectedExecutionHandler handler    // 拒绝策略
)

任务执行流程

java 复制代码
提交任务
    ↓
核心线程是否已满? ──否──→ 创建核心线程执行
    ↓是
任务队列是否已满? ──否──→ 加入队列等待
    ↓是
最大线程是否已满? ──否──→ 创建非核心线程执行
    ↓是
执行拒绝策略

内置线程池

拒绝策略

java 复制代码
// 四种内置策略
AbortPolicy           // 抛异常(默认)
CallerRunsPolicy      // 调用者线程执行
DiscardPolicy         // 丢弃,不抛异常
DiscardOldestPolicy   // 丢弃队列头,重提任务

1.5、Semaphore(信号量)

信号量,用来表示"可用资源的个数"。本质上就是⼀个计数器。

理解信号量

可以把信号量想象成是停车场的展示牌:当前有车位100个。表示有100个可用资源。

当有车开进去的时候,就相当于申请一个可用资源,可用车位就-1(这个称为信号量的P操作)。

当有车开出来的时候,就相当于释放一个可用资源,可用车位就+1(这个称为信号量的V操作)。

如果计数器的值已经为0了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源。

Semaphore的PV操作中的加减计数器操作都是原子的,可以在多线程环境下直接使用。

代码示例

java 复制代码
// 场景:限流,控制并发数
Semaphore semaphore = new Semaphore(5);  // 最多5个线程同时访问

void access() {
    semaphore.acquire();  // 获取许可
    try {
        // 业务代码
    } finally {
        semaphore.release(); // 释放许可
    }
}

二、线程安全的集合类

在 Java 中,线程安全的集合主要分为三类:早期的线程安全集合、同步包装器、JUC 并发集合。

2.1、早期线程安全集合

代码示例:

java 复制代码
// 示例:Vector 线程安全但性能差
Vector<String> vector = new Vector<>();
vector.add("a");      // 内部 synchronized
String s = vector.get(0);

缺点: 所有方法都加同一把锁(对象锁),并发度极低,即使读操作也互斥。

2.2、同步包装器

Collections.synchronizedXXX() 方法可以将普通集合包装成线程安全集合。

特点:

  • 基于 synchronized 代码块,锁对象可指定(默认是包装后的集合对象)。

  • 迭代器不是线程安全的,遍历时必须加锁。

  • 性能依然较差,因为所有操作都串行化。

2.3、JUC 并发集合(java.util.concurrent 包)

现代 Java 并发编程的首选,提供了细粒度锁、无锁算法等高效实现。

2.3.1、ConcurrentHashMap

线程安全的哈希表,JDK 1.8+ 采用 CAS + synchronized 锁头结点,并发度极高。

特点:

  • 不允许 null 键/值。

  • 迭代器是弱一致性的(不抛出 ConcurrentModificationException,但可能不反映最新修改)。

  • 适用于高并发读写。

2.3.2、CopyOnWriteArrayList / CopyOnWriteArraySet

写时复制 思想:写操作复制一个新数组,修改完成后替换原数组;读操作在原数组上进行,无需加锁。

特点:

  • 读多写少场景极佳(如配置信息、监听器列表)。

  • 写操作开销大(数组复制),不适合写频繁的场景。

  • 迭代器是快照风格,不会抛出 ConcurrentModificationException

2.3.3、ConcurrentLinkedQueue / ConcurrentLinkedDeque

无界非阻塞队列,基于 CAS 实现。

特点:

  • 无锁、高吞吐。

  • 不阻塞,适合生产者-消费者模型但不需要阻塞等待的场景。

  • 迭代器弱一致性。

2.3.4、BlockingQueue 接口及其实现

阻塞队列: 当队列满时,put() 阻塞;当队列空时,take() 阻塞。常用于生产者-消费者模式。

2.3.5、ConcurrentSkipListMap / ConcurrentSkipListSet

基于跳表的并发有序 Map/Set,类似于 TreeMap 的并发版本。

特点:

  • 线程安全且有序。

  • 无锁(CAS),并发性能优于 Collections.synchronizedSortedMap

  • 提供 lowerKeyceilingKey 等导航方法。

三、死锁

死锁是并发编程中的一种严重问题:两个或更多线程互相持有对方需要的资源,且都不释放自己占有的资源,导致所有线程都无法继续执行。

3.1、死锁的四个必要条件

四个条件必须同时满足才会发生死锁:

  • 互斥条件: 资源一次只能被一个线程占用。

  • 请求与保持条件: 线程持有至少一个资源,同时又在请求其他资源。

  • 不可剥夺条件: 线程已获得的资源,在使用完之前不能被其他线程强行剥夺。

  • 循环等待条件: 存在一个线程等待环,例如 T1 等待 T2 的资源,T2 等待 T3 的资源,...,Tn 等待 T1 的资源。

3.2、死锁的避免与预防

  • 破坏"请求与保持"条件:一次性申请所有资源

  • 破坏"不可剥夺"条件:使用可超时的锁

  • 破坏"循环等待"条件:按固定顺序获取锁

  • 使用高级并发工具(使用 ConcurrentHashMapBlockingQueue 等,避免显式加锁;使用 SemaphoreCountDownLatch 等,减少锁竞争。)

  • 避免嵌套锁(尽量减少同步块内调用外部方法(可能持有其他锁);能不用多个锁就不用。)

相关推荐
XMYX-04 小时前
03 - Go 常用类型速查表 + 实战建议(实战向)
开发语言·golang
橘子编程4 小时前
计算机内存与缓存完全指南
java·计算机网络·spring·缓存
杰克尼4 小时前
springCloud(day09-Elasticsearch02)
java·后端·spring·spring cloud
@atweiwei4 小时前
用 Rust 构建 LLM 应用的高性能框架
开发语言·后端·ai·rust·langchain·llm
云烟成雨TD4 小时前
Spring AI 1.x 系列【24】结构化输出 API
java·人工智能·spring
han_hanker4 小时前
springboot 不推荐使用@Autowired怎么处理
java·spring boot·后端
最初的↘那颗心4 小时前
LangChain4j入门:集成SpringBoot与核心概念全解析
java·spring boot·ai·大模型·langchain4j
计算机学姐4 小时前
基于SpringBoot的高校实验室预约管理系统
java·spring boot·后端·mysql·spring·信息可视化·tomcat
九转成圣4 小时前
实战记录:用 Java 拼接长图/网格图,我踩了哪些坑?
java·开发语言