面试系列:什么是JAVA并发编程中的JUC并发工具类

作者平台:

| CSDN:blog.csdn.net/qq\\_411539... 底)

| 掘金:juejin.cn/user/651387...

| 知乎:www.zhihu.com/people/1024...

| GitHub:github.com/JiangXia-10...

| 微信公众号:1024笔记

本文一共2529字,预计阅读13分钟

作为Java并发编程的核心武器库,JUC(java.util.concurrent)提供了一套基于AQS(AbstractQueuedSynchronizer)理论模型的完整并发控制解决方案。


一、JUC体系架构层次


二、AQS核心原理解析(并发控制的基石)

AQS,全称AbstractQueuedSynchronizer,即抽象队列同步器,是Java并发包(java.util.concurrent.locks)中的基础框架,由Doug Lea设计。它提供了一套用于实现线程同步机制的模板,主要通过一个原子整数状态(state)和一个先进先出(FIFO)等待队列来管理线程对共享资源的访问。

AQS的一些关键概念:

  1. 同步状态(State):AQS使用了一个volatile类型的int变量state来表示同步状态,这个状态可以用来表示锁的持有情况、许可数量等。提供了getState()、setState()以及compareAndSetState()方法来操作这个状态值。

  2. CLH等待队列:这是一个双向链表结构的队列,用于维护那些尝试获取资源但暂时失败的线程。每个节点代表一个线程,并包含有关该线程的信息,如它的状态、前驱和后继指针等。当线程竞争资源失败时,它们会被封装成Node并加入到队列的尾部。

  3. 独占模式与共享模式:

  • 独占模式(Exclusive Mode):同一时刻只有一个线程能够获取资源,比如ReentrantLock。

  • 共享模式(Shared Mode):允许多个线程同时获取资源,比如Semaphore和CountDownLatch。

  1. 模板方法模式:AQS的核心思想是模板方法模式,它将通用的线程排队、阻塞/唤醒等逻辑封装在抽象类中,而将具体的同步状态获取/释放逻辑延迟到子类去实现。开发者只需要重写几个钩子方法(例如tryAcquire、tryRelease),就可以快速构建自定义的同步器。

  2. ConditionObject:AQS还提供了一个内部类ConditionObject,用于支持类似synchronized关键字结合wait()/notify()的条件等待/通知机制,但是它可以支持多个条件队列。

1. 关键结构:CLH队列变种

arduino 复制代码
// 简化版AQS节点结构
static final class Node {
    volatile int waitStatus;   // 状态值:CANCELLED/SIGNAL/CONDITION/PROPAGATE
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 持有的线程
    Node nextWaiter;          // 条件队列专用
}

2. 状态管理机制

java 复制代码
public abstract class AbstractQueuedSynchronizer {
    private volatile int state;  // 核心状态变量
    // CAS修改状态
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    // 子类需要实现的模板方法
    protected boolean tryAcquire(int arg) { ... } // 独占获取
    protected int tryAcquireShared(int arg) { ... } // 共享获取
}

3. 工作流程(以ReentrantLock为例)


三、JUC工具类三大设计范式

1. 状态与行为分离

java 复制代码
// ReentrantLock 委托给Sync执行
public class ReentrantLock {
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();
        final boolean nonfairTryAcquire(int acquires) { ... }
    }
}

2. 模板方法定制

java 复制代码
public class CountDownLatch {
    private static final class Sync extends AbstractQueuedSynchronizer {
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        protected boolean tryReleaseShared(int releases) {
            // CAS减少状态值
            while (!compareAndSetState(current, next)) {...}
            return next == 0;
        }
    }
}

3. 无锁化协作

scss 复制代码
// LongAdder 分段累加消除竞争
public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        // 哈希选择分段
        int hash = getProbe();
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[hash & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended, hash);
    }
}

四、核心工具类理论模型对比


五、避免可见性与重排序问题

1. happen-before规则保障

先解释下什么happen-before规则

"Happens-before"是Java内存模型(JMM, Java Memory Model)中的一个概念,用于定义程序中操作之间的部分有序关系,确保多线程环境下的可见性和有序性问题得到解决。简单来说,如果操作A happens-before 操作B,那么操作A的结果对于操作B来说是可见的,并且在执行操作B时不会看到操作A之前的操作重排序。

以下是happens-before原则的一些关键规则:

  1. 程序顺序规则:每个线程内部的操作按照程序代码的顺序执行,即在单个线程内,程序的实际执行顺序必须与其代码顺序一致。虽然编译器和处理器可以对指令进行重排序优化,但是这种重排序不能改变单线程内的语义。

  2. 监视器锁规则:当一个线程释放了某个锁,并且另一个线程随后获取了同一个锁,那么在前者释放锁之前的动作happens-before于后者获取锁之后的动作。这保证了在使用synchronized关键字同步的临界区内的所有操作对于其他获取同一把锁进入该临界区的线程都是可见的。

  3. volatile变量规则:对volatile变量的写入操作happens-before于任何后续对该变量的读取操作。这意味着当一个线程写入一个volatile变量后,另一个线程读取这个变量时能看到最新的值,并且写操作之前的其他变量的状态也会对读取线程可见。

  4. 线程启动规则:Thread.start()方法调用happens-before于新线程开始执行后的所有操作。这意味着主线程中在启动子线程前的所有准备工作都对子线程可见。

  5. 线程终止规则:线程中的所有操作happens-before于其他线程通过Thread.join()方法成功返回。这意味着一个线程等待另一个线程结束后可以获得那个线程结束前所有的状态更新。

  6. 传递性:如果操作A happens-before操作B,而操作B又happens-before操作C,那么操作A happens-before操作C。

java 复制代码
// StampedLock的正确用法
public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();
    void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();   // 写锁保证内存可见性
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
}

2. 内存屏障类型对应


六、最佳实践黄金法则

1、选择合适锁的粒度

arduino 复制代码
// 错误:并发容器内滥用synchronized
ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>();
synchronized(map) {  // ⚠️破坏并发性
    map.put(key, value);
}

2、避免嵌套死锁

csharp 复制代码
// 使用定时锁规避
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        if (innerLock.tryLock(50, TimeUnit.MILLISECONDS)) { ... }
    } finally { ... }
}

3、线程池资源隔离

ini 复制代码
// 不同业务使用独立线程池
ExecutorService orderPool = Executors.newFixedThreadPool(5);
ExecutorService payPool = Executors.newFixedThreadPool(3);

思想总结

JUC的精髓在于"状态原子管理+队列化等待 "。理解AQS的state操作和CLH队列设计,就能掌握所有JUC工具类的运作本质,从而在分布式锁、流量控制、批量任务调度等场景游刃有余。

最后感谢大家的关注!

如果你觉得本文不错,就点赞分享给更多的人吧!

相关推荐
phltxy10 分钟前
ArrayList与顺序表
java·算法
Livingbody20 分钟前
【心理咨询师数字孪生对话数据集】标准化为 ShareGPT OpenAI 格式
后端
Doris_LMS23 分钟前
保姆级别IDEA关联数据库方式、在IDEA中进行数据库的可视化操作(包含图解过程)
java·mysql·postgresql
衍生星球38 分钟前
JSP 程序设计之 Web 技术基础
java·开发语言·jsp
Java编程乐园42 分钟前
Java函数式编程之【Stream终止操作】【下】【三】【收集操作collect()与分组分区】【下游收集器】
java
yinyan131444 分钟前
一起学springAI系列一:初体验
java·人工智能·ai
永卿0011 小时前
设计模式-责任链模式
java·设计模式·责任链模式
hello 早上好1 小时前
深入解析AOP调用链:递归与责任链模式的协同实现
java·责任链模式
wangmengxxw1 小时前
Spring-常用注解
java·数据库·spring·注解
籍籍川草1 小时前
JVM指针压缩的那些事
java·开发语言·jvm