AQS(AbstractQueuedSynchronizer)是 Java 中的一个强大的同步框架,为我们提供了实现各种同步器的基础。在本篇博客中,我们将介绍 AQS 框架的基本原理,并探讨几个常见的 AQS 实现:ReentrantLock、CountDownLatch 和 Semaphore。我们将了解它们的区别以及各自的优缺点。
AQS 是 Java 并发包中的核心部分
,它提供了一个基于 FIFO(first in first out 先进先出
)排队的双向链表等待队列,用于管理等待线程并控制资源的获取和释放。AQS 提供了一些核心的方法供子类继承和实现。下面我们重点介绍几个常见的 AQS 实现。
AQS 用于单个服务内部的线程同步和并发控制
。适用于所有单体架构,或者微服务项目中单个服务内部的线程同步和并发控制。不适用于分布式部署项目
,对于集群部署的微服务项目,需要额外考虑跨服务的分布式同步问题,并选择适合的分布式锁解决方案来实现分布式环境下的同步与控制。例如 ZooKeeper、Redisson 等提供的分布式锁实现
ReentrantLock 是 AQS 框架的一个常见应用,它提供了独占模式的锁
。多个线程可以重复获取和释放该锁,而不会因为重复获取而发生死锁。这个特性使得 ReentrantLock 可以用于更复杂的同步场景。
ReentrantLock内部使用了一个int变量来表示锁的持有状态,作为条件是占用资源,值为0时表示未锁定状态,大于0表示已锁定。
下面是一个简单的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock(); // 加锁,如果没有线程持有,则持有数加1返回。如果有线程持有,则持有数加1,并放到aqs等待队列排队
try {
// 执行保护的代码块
} finally {
lock.unlock(); // 释放锁,如果当前线程是此锁的持有者,则持有计数递减。如果保持计数现在为零,则释放锁。如果当前线程不是此锁的持有者,则抛出IllegalHonitorStateException
}
}
}
ReentrantLock 的优点是提供了可重入性,支持公平和非公平模式,并且具有更丰富的功能。缺点是相对于 synchronized 关键字有更高的复杂性和更多的开销。
CountDownLatch 是一个同步机制,它允许一个或多个线程等待其他线程完成操作
,它的构造器将会接收一个整数参数,表示需要等待的线程数量。
CountDownLatch内部也使用AQS的等待队列实现等待线程的阻塞,使用一个int类型的计数器来表示需要等待的线程数。
具体实现过程:
构造时将计数器初始化为传入的参数值。
countDown()方法会让计数器值减1,如果计数器变为0了,则唤醒等待队列中所有的线程
await()方法会首先将当前线程添加到AQS的等待队列中,然后判断计数器是否为0,如果不为0则一直等待。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private static CountDownLatch latch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
// 创建并启动多个线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行操作
latch.countDown(); // 操作完成后计数减1
}).start();
}
latch.await(); // 等待计数为0
// 所有操作完成后继续执行
}
}
CountDownLatch 的优点是简单易用,可以很方便地实现等待其他线程完成的场景。缺点是一旦计数值确定后就无法修改,并且只能使用一次。
Semaphore 是一种控制并发访问资源数量的同步工具。它允许多个线程同时访问共享资源
,但需要限制总的访问线程数量。Semaphore 通过 AQS 的计数和等待机制来控制线程的获取和释放。
Semaphore内部也是通过一个int值表示许可数量来实现的;
acquire()方法会获取一个许可,如果没有就排队等待;
release()方法会释放一个许可。
下面是一个简单的示例代码:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
// 创建并启动多个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
// 访问共享资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
}
}
Semaphore 的优点是可以控制资源的并发访问数量,可以用于限流等场景。缺点是相对于其他同步机制有更多的复杂性和开销。
锁定对象:
实现原理:
使用场景: