JUC源码分析—CAS和Unsafe

本文详细介绍了J.U.C包中CAS(Compare-and-Swap)的概念及其在并发控制中的应用,解释了CAS如何避免锁的竞争,讨论了ABA问题及解决方案,展示了AtomicInteger类的CAS实现细节。

在对J.U.C包的源码分析之前,首先介绍下一个比较重要的概念-CAS(Compare-and-Swap)。在J.U.C包中大量使用了CAS,涉及并发或资源争用的地方都使用了sun.misc.Unsafe类的方法进行CAS操作。


CAS

在针对多处理器操作而设计的处理器中提供了一些特殊指令,用于管理共享数据的并发访问。现在,几乎所有的现代处理器中都包含了某种形式的原子读-改-写指令,例如比较并交换(Compare-and-Swap)或者关联加载/条件存储(Load-Linked/Store-Conditional)。操作系统和JVM可以使用这些指令来实现锁和并发的数据结构。

CAS,compare and swap比较并替换。 CAS有三个参数:需要读写的内存位值(V)、进行比较的预期原值(A)和拟写入的新值(B)。当且仅当V的值等于A时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。简单来说,CAS的含义是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”。CAS是一项乐观的技术,它希望能成功地执行更新操作,并且如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。但是,失败的线程并不会被挂起(这就是与获取锁的机制不同之处),而是被告知在这次竞争中失败,并可以多次尝试。这种灵活性就大大减少了与锁相关的活跃性风险。

以AtomicInteger为例,内部的CAS实现如下:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //value的偏移地址
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

    private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

说明: 可以看到AtomicInteger内部都是使用了Unsafe类来进行CAS操作,valueOffset表示的是value值的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的, 偏移量可以简单理解为指针指向该变量的内存地址。 value使用volatile修饰,直接从共享内存中操作变量,保证多线程之间看到的value值是同一份。
以方法getAndUpdate()为例,执行步骤如下:

  1. 从内存中读取修改前的值prev,并执行给定函数式计算修改后的值next
  2. 调用compareAndSet修改value值(内部是调用了unsafe的compareAndSwapInt方法)。如果此时有其他线程也在修改这个value值,那么CAS操作就会失败,继续进入do循环重新获取新值,再次执行CAS直到修改成功。

ABA问题

ABA问题是一种异常现象:如果算法中的节点可以被循环利用,那么在使用“比较并替换”指令时就可能出现这种问题(主要在没有垃圾回收机制的环境中)。

如果有两个线程x和y,如果x初次从内存中读取变量值为A;线程y对它进行了一些操作使其变成B,然后再改回A,那么线程x进行CAS的时候就会误认为这个值没有被修改过。尽管CAS操作会成功执行,但是不代表它是没有问题的,如果有一个单向链表A B组成的栈,栈顶为A,线程T1准备执行CAS操作head.compareAndSet(A,B),在执行之前线程T2介入,T2将A、B出栈,然后又把C、A放入栈,T2执行完毕;切回线程T1,T1发现栈顶元素依然为A,也会成功执行CAS将栈顶元素修改为B,但因为B.next为null,所以栈结构就会丢弃C元素。

针对这种情况,有一种简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个和版本号,即这个值由A变为B,然后又变成A,版本号也将是不同的。Java中提供了AtomicStampedReferenceAtomicMarkableReference来解决ABA问题。他们支持在两个变量上执行原子的条件更新。AtomicStampedReference将更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题。 类似地,AtomicMarkableReference将更新一个“对象引用-布尔值”二元组,在某些算法中将通过这种二元组使节点保存在链表中同时又将其标记为“已删除的节点”。不过目前来说,这两个类比较鸡肋,大部分情况下的ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

Unsafe

Unsafe是实现CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。Unsafe类提供了硬件级别的原子操作。

Unsafe函数列表

///--------------------- peek and poke 指令--------------
//获取对象o中给定偏移地址(offset)的值。以下相关get方法作用相同
public native int getInt(Object o, long offset);
//在对象o的给定偏移地址存储数值x。以下set方法作用相同
public native void putInt(Object o, long offset, int x);
public native Object getObject(Object o, long offset);
public native void putObject(Object o, long offset, Object x);
/**篇幅原因,省略其他类型方法 */
//从给定内存地址获取一个byte。下同
public native byte    getByte(long address);
//在给定内存地址放置一个x。下同
public native void    putByte(long address, byte x);
/**篇幅原因,省略其他类型方法*/
//获取给定内存地址的一个本地指针
public native long getAddress(long address);
//在给定的内存地址处存放一个本地指针x
public native void putAddress(long address, long x);

///------------------内存操作----------------------
//在本地内存分配一块指定大小的新内存,内存的内容未初始化;它们通常被当做垃圾回收。
public native long allocateMemory(long bytes);
//重新分配给定内存地址的本地内存
public native long reallocateMemory(long address, long bytes);
//将给定内存块中的所有字节设置为固定值(通常是0)
public native void setMemory(Object o, long offset, long bytes, byte value);
//复制一块内存,double-register模型
public native void copyMemory(Object srcBase, long srcOffset,
                              Object destBase, long destOffset,
                              long bytes);
//复制一块内存,single-register模型
public void copyMemory(long srcAddress, long destAddress, long bytes) {
    copyMemory(null, srcAddress, null, destAddress, bytes);
}
//释放给定地址的内存
public native void freeMemory(long address);
//获取给定对象的偏移地址
public native long staticFieldOffset(Field f);
public native long objectFieldOffset(Field f);

//------------------数组操作---------------------------------
//获取给定数组的第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//获取给定数组的元素增量地址,也就是说每个元素的占位数
public native int arrayIndexScale(Class<?> arrayClass);

//------------------------------------------------------------
//告诉虚拟机去定义一个类。默认情况下,类加载器和保护域都来自这个方法
public native Class<?> defineClass(String name, byte[] b, int off, int len,
                                   ClassLoader loader,
                                   ProtectionDomain protectionDomain);
//定义匿名内部类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
//定位一个实例,但不运行构造函数
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

///--------------------锁指令(synchronized)-------------------------------
//对象加锁
public native void monitorEnter(Object o);
//对象解锁
public native void monitorExit(Object o);
public native boolean tryMonitorEnter(Object o);
//解除给定线程的阻塞
public native void unpark(Object thread);
//阻塞当前线程
public native void park(boolean isAbsolute, long time);

// CAS
public final native boolean compareAndSwapObject(Object o, long offset,
                                                 Object expected,
                                                 Object x);
//获取对象o的给定偏移地址的引用值(volatile方式)
public native Object getObjectVolatile(Object o, long offset);
public native void    putObjectVolatile(Object o, long offset, Object x);
/** 省略其他类型方法  */


//用于lazySet,适用于低延迟代码。
public native void    putOrderedObject(Object o, long offset, Object x);
/** 省略其他类型方法  */
//获取并加上给定delta,返回加之前的值
public final int getAndAddInt(Object o, long offset, int delta)
/** 省略其他类型方法  */
//为给定偏移地址设置一个新的值,返回设置之前的值
public final int getAndSetInt(Object o, long offset, int newValue)
/** 省略其他类型方法  */

///--------------------1.8新增指令-----------------------
// loadFence() 表示该方法之前的所有load操作在内存屏障之前完成
public native void loadFence();
//表示该方法之前的所有store操作在内存屏障之前完成
public native void storeFence();
//表示该方法之前的所有load、store操作在内存屏障之前完成,这个相当于上面两个的合体功能
public native void fullFence();

Unsafe的方法比较简单,直接看方法字面意思就大概知道方法的作用。
在Unsafe里有两个方法模型:
double-register模型:给定对象,给定偏移地址offset。从给定对象的偏移地址取值。如getInt(Object o, long offset)
single-register模型:给定内存地址,直接从给定内存地址取值,如getInt(long)

这里介绍一下几个比较重要的方法,在之后的源码阅读里会用到。

  1. arrayBaseOffset:操作数组,用于获取数组的第一个元素的偏移地址
  2. arrayIndexScale:操作数组,用于获取数组元素的增量地址,也就是说每个元素的占位数。打个栗子:如果有一个数组{1,2,3,4,5,6},它第一个元素的偏移地址为16,每个元素的占位是4,如果我们要获取数组中“5”这个数字,那么它的偏移地址就是16+4*4。
  3. putOrderedObject:putOrderedObject 是 lazySet 的实现,适用于低延迟代码。它能够实现非堵塞写入,避免指令重排序,这样它使用快速的存储-存储(store-store) barrier,而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的写操作上。这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后被其他线程看到。类似的方法还有putOrderedInt、putOrderedLong
  4. loadFencestoreFencefullFence:这三个方法是1.8新增,主要针对内存屏障定义,也是为了避免重排序:
  • loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。
  • storeFence()表示该方法之前的所有store操作在内存屏障之前完成。
  • fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。



作者:泰迪的bagwell
链接:https://www.jianshu.com/p/a897c4b8929f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

<think>嗯,用户之前让我介绍过Java多线程的基础知识,现在他们想深入了解JUC(java.util.concurrent)的源码。这说明用户可能已经掌握了多线程的基本概念使用方法,现在想更深入理解底层实现,提升自己的技术水平。 首先,我需要确定用户对JUC的了解程度。他们可能已经用过一些工具类,比如ReentrantLock或CountDownLatch,但现在想了解这些类内部的机制。用户可能是中高级开发者,希望优化代码性能,或者准备面试,需要深入理解并发框架。 接下来,我需要考虑如何组织内容。直接深入源码可能会让用户感到困惑,所以应该先从JUC的整体结构入手,然后分模块解析核心类。比如,原子类、锁、同步器、并发集合、线程池等,每个部分都需要挑选关键点进行讲解。 用户可能需要知道关键设计模式,比如AQS(AbstractQueuedSynchronizer),因为这是JUC的核心。需要详细解释AQS的工作原理,如CLH队列、状态管理等。此外,CAS操作volatile关键字也是实现无锁并发的基础,需要结合源码说明它们的作用。 另外,用户可能对常见类的实现感兴趣,比如ReentrantLock的公平锁与非公平锁区别,ConcurrentHashMap的分段锁或CAS优化,线程池的任务调度流程等。需要结合代码示例流程图,帮助用户理解这些复杂机制。 同时,要注意避免过于冗长的代码展示,而是聚焦在关键方法设计思路上。例如,AQS的acquirerelease方法,以及它们如何被ReentrantLock或Semaphore调用。此外,可以比较不同版本的实现变化,比如JDK1.71.8中ConcurrentHashMap的改进,让用户了解优化方向。 最后,需要给出学习建议,推荐用户如何逐步深入源码,比如从简单的AtomicInteger开始,再逐步研究AQS锁的实现。同时,提醒用户注意调试测试的重要性,通过实际案例加深理解。 总结下来,回答的结构应该是先概述JUC的重要性,再分模块解析核心源码,结合关键设计模式具体类实现,最后给出学习方法资源推荐。这样既系统又深入,帮助用户由浅入深地掌握JUC源码。</think>好的!`java.util.concurrent`(JUC)是 Java 并发编程的核心工具包,包含大量高性能、线程安全的工具类框架。深入源码理解其设计思想,能帮助你掌握高并发编程的精髓。以下从 **核心模块**、**设计模式**、**关键类源码分析** 三个维度进行详细解读。 --- ### **一、JUC 整体架构** JUC 的核心模块包括: 1. **原子类**(Atomic):基于 CAS 的无锁并发操作(如 `AtomicInteger`)。 2. **锁与同步器**(Locks):如 `ReentrantLock`、`AQS`(AbstractQueuedSynchronizer)。 3. **并发容器**(Collections):如 `ConcurrentHashMap`、`CopyOnWriteArrayList`。 4. **线程池**(Executors):`ThreadPoolExecutor`、`ForkJoinPool`。 5. **同步工具**(Tools):`CountDownLatch`、`CyclicBarrier`、`Semaphore`。 6. **并发流与 Future**:如 `CompletableFuture`、`ForkJoinTask`。 --- ### **二、核心源码解读** #### **1. AQS(AbstractQueuedSynchronizer)** AQS 是 JUC同步器的基石(如 `ReentrantLock`、`Semaphore` 均基于它实现)。 ##### **核心设计** - **状态变量(state)**:通过 `volatile int state` 表示资源状态(如锁的持有次数)。 - **CLH 队列**:一个双向链表实现的等待队列,存储竞争资源的线程。 - **模板方法模式**:子类只需实现 `tryAcquire`/`tryRelease`,AQS 负责线程排队唤醒。 ##### **源码关键方法** ```java // 独占模式获取资源(如 ReentrantLock.lock()) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 子类需实现 tryAcquire(如判断 state 是否为0) protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } ``` ##### **实现示例:ReentrantLock 的公平锁** ```java // 公平锁的 tryAcquire 实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 关键:检查队列是否有等待线程 compareAndSetState(0, acquires)) { // CAS 更新状态 setExclusiveOwnerThread(current); return true; } } // 处理重入逻辑(state 增加) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; } ``` --- #### **2. 原子类(AtomicInteger)** 原子类的核心是 **CAS(Compare-And-Swap)** 操作,直接依赖 CPU 指令(如 x86 的 `cmpxchg`)。 ##### **源码关键实现** ```java public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value; // 通过 volatile 保证可见性 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } } // Unsafe 类的 CAS 操作(native 方法) public final class Unsafe { public final native boolean compareAndSwapInt( Object o, long offset, int expected, int x); } ``` ##### **CAS 的 ABA 问题** - **问题**:变量从 A 改为 B 又改回 A,CAS 无法感知中间变化。 - **解决方案**:使用 `AtomicStampedReference` 添加版本号。 --- #### **3. ConcurrentHashMap(JDK 1.8)** JDK 1.8 摒弃分段锁,改用 **CAS + synchronized** 实现更细粒度的锁。 ##### **关键设计** - **Node 数组(table)**:每个桶位(bucket)是一个链表或红黑树。 - **并发控制**: - **CAS 初始化数组**:避免多线程重复初始化。 - **synchronized 锁桶头节点**:仅锁定单个桶,减少竞争。 ##### **putVal 方法核心逻辑** ```java final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); // CAS 初始化数组 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // CAS 插入新节点 } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); // 协助扩容 else { synchronized (f) { // 锁住桶头节点 // 插入链表或红黑树 } } } } ``` --- #### **4. ThreadPoolExecutor** 线程池的核心是 **任务队列** **线程调度策略**。 ##### **核心参数** - **corePoolSize**:核心线程数(即使空闲也不销毁)。 - **workQueue**:任务队列(如 `LinkedBlockingQueue`)。 - **RejectedExecutionHandler**:拒绝策略(如丢弃、抛异常)。 ##### **execute 方法逻辑** ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) // 创建新线程执行任务 return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { // 任务入队 // 二次检查线程池状态 } else if (!addWorker(command, false)) // 尝试创建非核心线程 reject(command); // 触发拒绝策略 } ``` --- ### **三、JUC 设计模式与技巧** 1. **CAS 无锁编程** - 通过 `Unsafe` 类直接操作内存,避免锁竞争(如原子类、ConcurrentHashMap)。 - 适用场景:读多写少,竞争不激烈。 2. **模板方法模式** - AQS 将公共逻辑(如线程排队)抽象到基类,子类只需实现特定方法(如 `tryAcquire`)。 3. **写时复制(Copy-On-Write)** - `CopyOnWriteArrayList` 在修改时复制整个数组,保证读操作无锁。 4. **分段锁优化** - JDK 1.7 的 `ConcurrentHashMap` 使用分段锁(Segment),减少锁粒度。 --- ### **四、高频源码面试题** 1. **AQS 的 CLH 队列为什么是双向链表?** - 答:为了支持取消操作(如超时或中断),需要快速定位前驱节点。 2. **ConcurrentHashMap 1.7 1.8 的区别?** - 1.7:分段锁 + ReentrantLock; - 1.8:CAS + synchronized + 红黑树。 3. **线程池如何回收空闲线程?** - 答:`ThreadPoolExecutor` 通过 `getTask()` 中的 `poll(keepAliveTime)` 实现超时回收。 --- ### **五、学习建议** 1. **调试源码**:通过 IDE 调试 `ReentrantLock.lock()` 的执行流程。 2. **阅读注释**:JUC 类的源码注释通常包含设计思路并发规则。 3. **手写简化版**:尝试实现一个简单的 AQS 或线程池。 理解 JUC 源码不仅能提升并发编程能力,还能深入掌握 Java 的高性能设计哲学。建议从 `AtomicInteger` `ReentrantLock` 入手,逐步扩展到复杂类如 `ConcurrentHashMap` `ThreadPoolExecutor`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值