ReentrantLock 重入锁:深入解析与实战应用

ReentrantLock 重入锁:深入解析与实战应用

在多线程编程中,锁机制是保证线程安全的重要手段。ReentrantLock 是 Java 中实现 Lock 接口的一个常用类,它支持重入性,即同一个线程可以多次获取同一个锁而不会被阻塞。本文将深入探讨 ReentrantLock 的实现原理、使用方法以及与 synchronized 关键字的对比。

1 ReentrantLock 的实现原理

ReentrantLock 的核心在于其重入性,即允许同一个线程多次获取同一个锁。为了实现这一特性,ReentrantLock 需要解决两个关键问题:

线程重入问题:如果当前线程已经持有锁,再次获取锁时应该直接成功。锁的释放问题:锁被获取了 n 次,必须被释放 n 次后,锁才算完全释放。

1.1 重入性的实现

ReentrantLock 通过 AQS(AbstractQueuedSynchronizer)框架来实现其同步语义。以非公平锁为例,核心方法 nonfairTryAcquire 的实现如下:

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

//1. 如果该锁未被任何线程占有,该锁能被当前线程获取

if (c == 0) {

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

//2.若被占有,检查占有线程是否是当前线程

else if (current == getExclusiveOwnerThread()) {

// 3. 再次获取,计数加一

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

步骤1:如果锁未被任何线程持有(c == 0),则尝试通过 CAS 操作获取锁。步骤2:如果锁已经被当前线程持有(current == getExclusiveOwnerThread()),则增加同步状态(state)的值,表示重入次数加一。

1.2 锁的释放

锁的释放逻辑在 tryRelease 方法中实现:

protected final boolean tryRelease(int releases) {

//1. 同步状态减1

int c = getState() - releases;

if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

boolean free = false;

if (c == 0) {

//2. 只有当同步状态为0时,锁成功被释放,返回true

free = true;

setExclusiveOwnerThread(null);

}

// 3. 锁未被完全释放,返回false

setState(c);

return free;

}

步骤1:减少同步状态的值。步骤2:只有当同步状态为 0 时,锁才算完全释放,返回 true。步骤3:如果同步状态不为 0,表示锁还未完全释放,返回 false。

2 公平锁与非公平锁

ReentrantLock 支持两种锁模式:公平锁和非公平锁。

非公平锁:线程获取锁的顺序可能与请求锁的顺序不同,可能导致某些线程获取锁的速度较快。公平锁:线程按照请求锁的顺序获取锁,即先到先得(FIFO)。

ReentrantLock 的构造方法默认创建非公平锁:

public ReentrantLock() {

sync = new NonfairSync();

}

也可以通过传入 boolean 值来选择公平锁或非公平锁:

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

公平锁在获取锁时会检查当前线程是否有前驱节点(hasQueuedPredecessors),以确保锁的获取顺序符合公平性原则。核心方法为:

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0)

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。

3 ReentrantLock 的使用

ReentrantLock 的使用方式与 synchronized 关键字类似,通过加锁和释放锁来实现同步。以下是一个简单的示例:

public class ReentrantLockTest {

private static final ReentrantLock lock = new ReentrantLock();

private static int count = 0;

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(() -> {

for (int i = 0; i < 10000; i++) {

lock.lock();

try {

count++;

} finally {

lock.unlock();

}

}

});

Thread thread2 = new Thread(() -> {

for (int i = 0; i < 10000; i++) {

lock.lock();

try {

count++;

} finally {

lock.unlock();

}

}

});

thread1.start();

thread2.start();

thread1.join();

thread2.join();

System.out.println(count);

}

}

在这个示例中,两个线程分别对 count 变量进行 10000 次累加操作,最终输出 count 的值为 20000,说明 ReentrantLock 支持重入性。

4 ReentrantLock 与 synchronized 的对比

ReentrantLock 与 synchronized 都是 Java 中常用的同步机制,但它们在实现和使用上有一些区别:

实现方式:ReentrantLock 是一个类,而 synchronized 是 Java 中的关键字。灵活性:ReentrantLock 可以绑定多个 Condition,实现多路通知;而 synchronized 只能通过 wait 和 notify/notifyAll 实现单路通知。锁的释放:ReentrantLock 必须手动释放锁,通常在 finally 块中调用 unlock 方法;而 synchronized 会自动释放锁,当同步块执行完毕时,由 JVM 自动释放。性能:在高竞争环境下,ReentrantLock 通常提供更好的性能;而在低竞争环境下,两者的性能差距不大。

以下是一个简单的性能比较示例:

import java.util.concurrent.locks.ReentrantLock;

public class PerformanceTest {

private static final int NUM_THREADS = 10;

private static final int NUM_INCREMENTS = 1_000_000;

private int count1 = 0;

private int count2 = 0;

private final ReentrantLock lock = new ReentrantLock();

private final Object syncLock = new Object();

public void increment1() {

lock.lock();

try {

count1++;

} finally {

lock.unlock();

}

}

public void increment2() {

synchronized (syncLock) {

count2++;

}

}

public static void main(String[] args) throws InterruptedException {

PerformanceTest test = new PerformanceTest();

// Test ReentrantLock

long startTime = System.nanoTime();

Thread[] threads = new Thread[NUM_THREADS];

for (int i = 0; i < NUM_THREADS; i++) {

threads[i] = new Thread(() -> {

for (int j = 0; j < NUM_INCREMENTS; j++) {

test.increment1();

}

});

threads[i].start();

}

for (Thread thread : threads) {

thread.join();

}

long endTime = System.nanoTime();

System.out.println("ReentrantLock time: " + (endTime - startTime) + " ns");

// Test synchronized

startTime = System.nanoTime();

for (int i = 0; i < NUM_THREADS; i++) {

threads[i] = new Thread(() -> {

for (int j = 0; j < NUM_INCREMENTS; j++) {

test.increment2();

}

});

threads[i].start();

}

for (Thread thread : threads) {

thread.join();

}

endTime = System.nanoTime();

System.out.println("synchronized time: " + (endTime - startTime) + " ns");

}

}

输出结果可能如下:

ReentrantLock time: 158786700 ns

synchronized time: 499476900 ns

5 总结

本文详细介绍了 ReentrantLock 的实现原理、使用方法以及与 synchronized 关键字的对比。ReentrantLock 通过 AQS 框架实现了重入性,支持公平锁和非公平锁,并且在高竞争环境下通常具有更好的性能。在实际开发中,根据具体需求选择合适的锁机制,可以有效提升程序的并发性能。

通过本文的学习,相信读者对 ReentrantLock 有了更深入的理解,能够在实际项目中灵活运用这一强大的同步工具。

6 思维导图

7 参考链接

深入理解Java并发重入锁ReentrantLock

相关推荐

八字稱骨算命 四兩一錢
亚洲365bet日博

八字稱骨算命 四兩一錢

09-29 👁️ 5179
无缘美加墨,智利队连续三届世界杯未能闯进正赛
365bet最新备用网站

无缘美加墨,智利队连续三届世界杯未能闯进正赛

10-23 👁️ 1419
刘备生肖属什么,刘备是什么属相
365bet最新备用网站

刘备生肖属什么,刘备是什么属相

10-07 👁️ 6380
江西特产苦槠豆腐制作过程
365bet最新备用网站

江西特产苦槠豆腐制作过程

08-14 👁️ 419