Java 并发编程:ReentrantLock 锁与 AQS
在 Java 中进行并发编程时,线程安全是一个重要的考量。Java 提供了多种线程同步的手段,其中 ReentrantLock
是一种常用的锁机制,它是 java.util.concurrent
包的一部分,具有比传统的 synchronized
关键字更灵活的特性。ReentrantLock
的底层实现依赖于一个名为 AQS(AbstractQueuedSynchronizer)的框架。
ReentrantLock 概述
ReentrantLock
是一种可重入锁,也就是说同一个线程可以多次获得该锁而不会出现死锁的情况。与 synchronized
相比,ReentrantLock
提供了更丰富的功能,如公平性、可中断的锁获取、锁的超时机制等。
主要特性:
- 可重入性:同一个线程可以重复获取锁。
- 公平性:可以选择公平锁或非公平锁,公平锁会按请求顺序获取锁。
- 可中断:可以使用
lockInterruptibly()
方法,使得在锁不可用时可以响应中断。 - 超时锁:可以设置一个超时时间来尝试获取锁。
AQS 概述
AQS(AbstractQueuedSynchronizer)是一个用于构建锁、同步器的底层框架,它提供了一种先进的队列机制来帮助管理线程的获取和释放锁。其主要工作是维护一个公平或非公平的队列,确保多个线程对共享资源的安全访问。
AQS 通过内置状态(即 state
)来表示锁的状态,当 state
为 0 时表示锁是未被占用的;当 state
大于 0 时表示锁被占用。
AQS 的工作流程:
- 通过
compareAndSetState()
方法修改状态。 - 当锁被占用时,后续请求锁的线程会被放入等待队列。
- 线程释放锁时会调用
release()
方法,如果这个线程是持锁线程,AQS 将改变state
值,并唤醒等待队列中的下一个线程。
代码示例
下面是一个使用 ReentrantLock
的简单示例,演示了基本的锁操作:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private static ReentrantLock lock = new ReentrantLock();
private static int count = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> incrementCount(1000), "Thread-1");
Thread thread2 = new Thread(() -> incrementCount(1000), "Thread-2");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count);
}
private static void incrementCount(int times) {
for (int i = 0; i < times; i++) {
lock.lock(); // 获取锁
try {
count++; // 对共享资源的操作
} finally {
lock.unlock(); // 释放锁
}
}
}
}
解析示例
在上述示例中,我们定义了一个全局计数器 count
,并通过两个线程并发地对它进行递增操作。每个线程在执行递增操作前调用 lock()
方法获取锁,确保不会发生线程安全问题。在 finally
块中释放锁,确保无论操作成功与否,锁都能被释放,避免死锁。
总结
ReentrantLock
和 AQS 提供了强大的并发控制能力,能够有效管理多线程对共享资源的访问。尽管使用锁可以解决线程安全问题,但也可能引入其他复杂性,例如死锁、饥饿等。因此,在使用这些工具时,需要谨慎设计,并合理使用锁的特性,包括选择合适的锁类型和锁策略。