JUC (Java Util Concurrency) 基础内容概述
编者注:本文为历史博文归档,内容涉及 JDK、框架与工具链版本请以当前官方文档为准。文中引用外链图片可能失效,阅读时请注意时效性。
1. JUC 概况
以下是 Java JUC(java.util.concurrent)包的主体结构:

- Atomic:AtomicInteger 等原子类
- Locks:Lock, Condition, ReadWriteLock
- Collections:Queue, ConcurrentMap 等并发集合
- Executors 框架:Future, Callable, Executor
- Tools:CountDownLatch, CyclicBarrier, Semaphore
2. 原子操作
当多个线程执行一个操作时,如果其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的(Atomic)。引入原子操作的主要原因是为了降低 synchronized 带来的性能开销。
以下以 AtomicInteger 为例,介绍其常用方法:
int addAndGet(int delta):以原子方式将给定值与当前值相加。实际上等于线程安全版本的i = i + delta操作。boolean compareAndSet(int expect, int update):如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。如果成功返回true,否则返回false且不修改原值。int decrementAndGet():以原子方式将当前值减 1。相当于线程安全版本的--i操作。int getAndAdd(int delta):以原子方式将给定值与当前值相加。相当于线程安全版本的t=i; i+=delta; return t;操作。int getAndDecrement():以原子方式将当前值减 1。相当于线程安全版本的i--操作。int getAndIncrement():以原子方式将当前值加 1。相当于线程安全版本的i++操作。int getAndSet(int newValue):以原子方式设置为给定值,并返回旧值。相当于线程安全版本的t=i; i=newValue; return t;操作。int incrementAndGet():以原子方式将当前值加 1。相当于线程安全版本的++i操作。
3. 指令重排
程序的执行顺序并不能总是保证符合代码编写的顺序,CPU 为了优化性能可能会进行指令重排。
要保证程序的最终结果等同于它在严格的顺序化环境下的结果,指令的执行顺序就可能与代码的顺序不一致。

在多核 CPU 高压力下,两个线程交替执行,x,y 的输出结果不确定。可能的结果如下:
| 序号 | 可能结果 |
|---|---|
| 1 | x = 0,y = 1 |
| 2 | x = 1,y = 1 |
| 3 | x = 1,y = 0 |
| 4 | x = 0,y = 0 |
4. Happens-before 法则(Java 内存模型)
如果动作 B 要看到动作 A 的执行结果(无论 A/B 是否在同一个线程里面执行),那么 A/B 就需要满足 happens-before 关系。
Happens-before 规则
- Program order rule:同一个线程中的每个 Action 都 happens-before 于出现在其后的任何一个 Action。
- Monitor lock rule:对一个监视器的解锁 happens-before 于每一个后续对同一个监视器的加锁。
- Volatile variable rule:对 volatile 字段的写入操作 happens-before 于每一个后续的同一个字段的读操作。
- Thread start rule:
Thread.start()的调用会 happens-before 于启动线程里面的动作。 - Thread termination rule:Thread 中的所有动作都 happens-before 于其他线程检查到此线程结束,或者
Thread.join()中返回,或者Thread.isAlive() == false。 - Interruption rule:一个线程 A 调用另一个线程 B 的
interrupt()都 happens-before 于线程 A 发现 B 被 A 中断(B 抛出异常或者 A 检测到 B 的isInterrupted()或者interrupted())。 - Finalizer rule:一个对象构造函数的结束 happens-before 与该对象的 finalizer 的开始。
- Transitivity:如果 A 动作 happens-before 于 B 动作,而 B 动作 happens-before 于 C 动作,那么 A 动作 happens-before 于 C 动作。
因为 CPU 可以不按我们写代码的顺序执行内存的存取过程(即指令乱序或并行运行),只有上面的 happens-before 所规定的情况下,才保证顺序性。
JMM 的特性
- 多个 CPU 之间的缓存也不保证实时同步。
- JMM 不保证创建过程的原子性,读写并发时,可能看到不完整的对象(因此涉及双重检查锁定 DCL 时需注意)。
volatile 语义
volatile 实现了类似 synchronized 的语义,却又没有锁机制。它确保对 volatile 字段的更新以可预见的方式告知其他的线程。
- 禁止重排序:Java 存储模型不会对 volatile 指令的操作进行重排序,保证对 volatile 变量的操作是按照指令的出现顺序执行的。
- 内存可见性:volatile 变量不会被缓存在寄存器中(只拥有线程可见性),每次总是从主存中读取 volatile 变量的结果。
注意:volatile 并不能保证线程安全,也就是说 volatile 字段的操作不是原子性的,volatile 变量只能保证可见性。5. CAS 操作
CAS (Compare and Swap) 是一种乐观锁机制。
CAS 有 3 个操作数:内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。
实现简单的非阻塞算法示例:
private volatile int value; // 借助 volatile 原语,保证线程间的数据是可见的
public final int get() {
return value;
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
} // Spin 自旋等待直到返回为止
}整个 J.U.C 包都是建立在 CAS 之上的。对于 synchronized 阻塞算法,J.U.C 在性能上有了很大的提升。但 CAS 会出现所谓的 "ABA" 问题。
6. Lock 锁
Synchronized 属于独占锁,高并发时性能不高。JDK 5 以后开始用 JNI 实现更高效的锁操作。
锁类层次结构:
Lock
- ReentrantLock
ReentrantReadWriteLock
- ReadLock
- WriteLock
- LockSupport
- Condition
Lock 接口主要方法:
| 方法名称 | 作用 |
|---|---|
void lock() | 获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。 |
void lockInterruptibly() throws InterruptedException | 如果当前线程未被中断,则获取锁。如果锁可用,则获取锁,并立即返回。 |
Condition newCondition() | 返回绑定到此 Lock 实例的新 Condition 实例。 |
boolean tryLock() | 仅在调用时锁为空闲状态才获取该锁。 |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。 |
void unlock() | 释放锁。 |
最佳实践:
- 一般来说,获取锁和释放锁是成对的操作,这样可以避免死锁和资源的浪费。
- 务必在
finally块中执行释放锁的操作。
7. AQS (AbstractQueuedSynchronizer)
AQS 是锁机制实现的核心所在,是 Lock/Executor 实现的前提。

AQS 实现原理
基本的思想是表现为一个同步器,AQS 支持下面两个核心操作:
acquire (获取同步状态):
while (synchronization state does not allow acquire) {
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;release (释放同步状态):
update synchronization state;
if (state may permit a blocked thread to acquire)
unpark one or more queued threads;要支持这两个操作,需要实现三个条件:
- Atomically managing synchronization state:原子性操作同步器的状态位。
- Blocking and unblocking threads:阻塞和唤醒线程。
- Maintaining queues:维护一个有序的队列。
1. 原子性地管理同步状态
使用一个 32 位整数来描述状态位:private volatile int state; 对其进行 CAS 操作,确保值的正确性。
2. 阻塞和唤醒线程
JDK 5.0 以后利用 JNI 在 LockSupport 类中实现了线程的阻塞和唤醒。
LockSupport.park():在当前线程中调用,导致线程阻塞。LockSupport.park(Object blocker)LockSupport.unpark(Thread thread)
3. 维护队列
在 AQS 中采用 CLH 队列 来解决有序队列的问题(CLH = Craig, Landin, and Hagersten)。

Node 里面是什么结构?

WaitStatus –> 节点的等待状态,一个节点可能位于以下几种状态:
CANCELLED = 1:节点操作因为超时或者对应的线程被 interrupt。节点不应该留在此状态,一旦达到此状态将从 CLH 队列中踢出。SIGNAL = -1:节点的继任节点是(或者将要成为)BLOCKED 状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpark())它的继任节点。CONDITION = -2:表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。0:正常状态,新生的非 CONDITION 节点都是此状态。
非负值标识节点不需要被通知(唤醒)。
队列管理操作:
入队 (enqueue):
采用 CAS 操作,每次比较尾结点是否一致,然后插入到尾结点中。
do {
pred = tail;
} while (!compareAndSet(pred, tail, node));出队 (dequeue):
while (pred.status != RELEASED);
head = node;
加锁操作:
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquireQueued(addWaiter(Node.EXCLUSIVE), arg);
selfInterrupt();
}释放操作:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}"The synchronizer framework provides a ConditionObject class for use by synchronizers that maintain exclusive synchronization and conform to the Lock interface." —— Doug Lea《The java.util.concurrent Synchronizer Framework》
以下是 AQS 队列和 Condition 队列的出入结点的示意图,可以通过这几张图看出线程结点在两个队列中的出入关系和条件。



说明:本文内容基于 JDK 5/6 时期的技术背景整理,部分实现细节在后续 JDK 版本中可能有所优化或调整,具体请以最新官方文档为准。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/juc-java-util-concurrency-ji-chu-nei-rong-gai-shu.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。