Java的Lock接口与AQS原理详解
一、Lock接口
1. 简介
在 Java 中,Lock
接口是 Java 并发包(java.util.concurrent.locks
)提供的一个用于实现锁机制的接口。相比于传统的 synchronized
关键字,Lock
接口提供了更灵活、更强大的锁控制功能,例如可中断锁、公平锁、尝试获取锁等。
2. 主要方法
lock()
:获取锁,如果锁不可用,则当前线程将被阻塞,直到锁被释放。lockInterruptibly()
:可中断地获取锁,在获取锁的过程中可以响应中断。tryLock()
:尝试获取锁,如果锁可用则立即返回true
,否则返回false
,不会阻塞当前线程。tryLock(long time, TimeUnit unit)
:在指定的时间内尝试获取锁,如果在该时间内获取到锁则返回true
,否则返回false
。unlock()
:释放锁。newCondition()
:返回一个与该锁关联的Condition
对象,用于线程间的等待/通知机制。
3. 示例代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// 临界区代码
System.out.println("线程 " + Thread.currentThread().getName() + " 进入临界区");
Thread.sleep(1000);
System.out.println("线程 " + Thread.currentThread().getName() + " 离开临界区");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
LockExample example = new LockExample();
Thread t1 = new Thread(example::doSomething);
Thread t2 = new Thread(example::doSomething);
t1.start();
t2.start();
}
}
二、AQS(AbstractQueuedSynchronizer)原理
1. 简介
AbstractQueuedSynchronizer
(简称 AQS)是 Java 并发包中实现锁和其他同步组件的基础框架,它位于 java.util.concurrent.locks
包中。AQS 提供了一个基于 FIFO 队列的同步机制,通过维护一个状态变量(int state
)和一个线程等待队列来实现锁的获取和释放。
2. 核心思想
AQS 的核心思想是使用一个 volatile
修饰的 int
类型的状态变量 state
来表示锁的状态,通过 CAS(Compare-And-Swap)操作来原子性地修改这个状态变量。当一个线程尝试获取锁时,如果 state
为 0 表示锁未被占用,该线程可以通过 CAS 操作将 state
置为 1 来获取锁;如果 state
不为 0 表示锁已被占用,该线程将被放入等待队列中阻塞等待。
3. 主要方法
getState()
:获取当前同步状态。setState(int newState)
:设置当前同步状态。compareAndSetState(int expect, int update)
:使用 CAS 操作设置当前状态,期望状态为expect
,更新状态为update
。tryAcquire(int arg)
:尝试以独占模式获取锁,需要子类实现。tryRelease(int arg)
:尝试以独占模式释放锁,需要子类实现。tryAcquireShared(int arg)
:尝试以共享模式获取锁,需要子类实现。tryReleaseShared(int arg)
:尝试以共享模式释放锁,需要子类实现。
4. 等待队列
AQS 使用一个双向链表作为等待队列,当一个线程尝试获取锁失败时,会被封装成一个节点加入到队列的尾部。队列中的线程会按照 FIFO 的顺序依次尝试获取锁。
三、基于 AQS 实现的锁
1. ReentrantLock
ReentrantLock
是一个可重入的互斥锁,它实现了 Lock
接口,底层基于 AQS 实现。可重入意味着同一个线程可以多次获取同一把锁而不会发生死锁。
示例代码
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void recursiveMethod(int count) {
lock.lock();
try {
if (count == 0) {
return;
}
System.out.println("递归层级: " + count);
recursiveMethod(count - 1);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
example.recursiveMethod(3);
}
}
2. ReentrantReadWriteLock
ReentrantReadWriteLock
是一个可重入的读写锁,它维护了一对锁,一个读锁和一个写锁。读锁可以被多个线程同时持有,而写锁是独占的,同一时间只能有一个线程持有写锁。读写锁的设计可以提高并发性能,适用于读多写少的场景。
示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private int data = 0;
public int readData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
public void writeData(int newData) {
writeLock.lock();
try {
data = newData;
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
ReentrantReadWriteLockExample example = new ReentrantReadWriteLockExample();
// 写入数据
new Thread(() -> example.writeData(10)).start();
// 读取数据
new Thread(() -> System.out.println("读取到的数据: " + example.readData())).start();
}
}
四、总结
Lock
接口为 Java 提供了更灵活的锁控制机制,而 AQS 作为实现锁和其他同步组件的基础框架,通过维护状态变量和等待队列,为锁的实现提供了强大的支持。ReentrantLock
和 ReentrantReadWriteLock
是基于 AQS 实现的典型锁,它们在不同的场景下可以发挥出各自的优势,提高程序的并发性能。