深入理解ReentrantLock
在Java并发编程中,锁(Lock)是控制多个线程对共享资源访问的重要工具。虽然Synchronized
关键字是实现锁的常用方式,但它在功能上比较有限。ReentrantLock
是java.util.concurrent.locks
包中提供的一个更加灵活和强大的锁实现,它是一个可重入的互斥锁,它允许线程在获取锁之后再次获取该锁而不被阻塞。简单来说,如果一个线程已经持有了一个锁,再次请求获取该锁时,它可以成功获取而不会被阻塞。
ReentrantLock的基本用法
使用ReentrantLock
的基本步骤包括创建锁实例、加锁和解锁。以下是一个简单的例子:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock();
try {
// 关键代码段
System.out.println("Lock is held by: " + Thread.currentThread().getName());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Runnable task = () -> example.performTask();
Thread t1 = new Thread(task, "Thread-1");
Thread t2 = new Thread(task, "Thread-2");
t1.start();
t2.start();
}
}
在这个例子中,我们创建了一个ReentrantLock
实例,并在performTask
方法中使用lock.lock()
来获取锁,lock.unlock()
来释放锁。在try
块中执行关键代码以确保在任何情况下都会释放锁。
ReentrantLock的高级功能
可重入性
正如前面提到的,ReentrantLock
是可重入的。这意味着同一个线程可以多次获取同一个锁而不会陷入死锁。以下示例展示了这一特性:
public void reentrantLockExample() {
lock.lock();
try {
System.out.println("First lock acquired.");
lock.lock();
try {
System.out.println("Second lock acquired.");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
公平锁与非公平锁
ReentrantLock
可以被配置为公平锁或非公平锁。公平锁意味着线程将以请求锁的顺序(FIFO)获得锁,而非公平锁则可能让某些线程“插队”。默认情况下,ReentrantLock
是非公平的。要创建公平锁,可以使用以下代码:
ReentrantLock fairLock = new ReentrantLock(true);
可中断锁获取
ReentrantLock
提供了一种机制,可以让线程在尝试获取锁时被中断。使用lock.lockInterruptibly()
方法,可以在获取锁的过程中响应中断:
public void performInterruptibleTask() {
try {
lock.lockInterruptibly();
try {
// 关键代码段
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断
}
}
超时锁获取
ReentrantLock
还允许在获取锁时指定超时时间。如果在指定时间内无法获取锁,线程将会放弃获取锁的尝试:
public void performTimedTask() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 关键代码段
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock within 5 seconds");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断
}
}
ReentrantLock与Synchronized的对比
灵活性
ReentrantLock
比Synchronized
更加灵活,提供了可中断的锁获取、超时锁获取和公平锁等特性。而Synchronized
则相对简单,只能用于基本的互斥锁定。
性能
在高竞争环境下,ReentrantLock
可能表现得比Synchronized
更好,因为它内部使用了一些优化策略,比如自旋锁。
可见性
ReentrantLock
提供了isHeldByCurrentThread()
等方法,可以方便地检测锁的状态,而Synchronized
则没有这种直接的方法。
ReentrantLock的实现原理
ReentrantLock
的核心是一个内部类 Sync
,它继承自 AbstractQueuedSynchronizer
(AQS)。AQS 是一个框架,用于实现依赖先进先出(FIFO)队列的阻塞锁和相关的同步器。
AQS的CAS操作
AQS 使用一个 volatile
类型的 int
变量 state
来表示同步状态,并提供了多种操作该变量的方法,其中包括 CAS 操作。这些方法主要包括:
compareAndSetState(int expect, int update)
:原子地将state
的值从expect
更新为update
。getState()
:获取当前state
的值。setState(int newState)
:设置state
的值。
这些方法确保了对同步状态的修改是线程安全的。
ReentrantLock的CAS实现
当我们调用 ReentrantLock.lock()
方法时,实际上调用的是 Sync.lock()
方法:
public void lock() {
sync.lock();
}
对于非公平锁,NonfairSync
实现了 lock
方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
在这个方法中,compareAndSetState(0, 1)
是一个 CAS 操作,它尝试将 state
从 0 更新为 1。如果成功,则说明当前线程获得了锁,并将当前线程设置为持有锁的线程。否则,调用 acquire(1)
进入同步队列进行排队等待。
对于公平锁,FairSync
实现了 lock
方法:
final void lock() {
acquire(1);
}
公平锁直接调用 acquire(1)
方法,而 acquire
方法内部也使用了 CAS 操作来尝试获取锁:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
方法由 Sync
的子类实现,用于尝试获取锁。非公平锁和公平锁分别实现了各自的 tryAcquire
方法,其中也使用了 CAS 操作。