目录
在并发编程领域,ReentrantLock
是一个非常重要的锁机制。这篇博客我们将深入探讨并发编程篇 16 中涉及的ReentrantLock
的实现原理。
一、ReentrantLock
简介
ReentrantLock
是一个可重入的互斥锁,它和synchronized
关键字类似,但提供了更强大的功能,比如可以尝试非阻塞地获取锁、能被中断地获取锁以及超时获取锁等。它位于java.util.concurrent.locks
包中。
二、ReentrantLock
的实现原理
- 基于 AQS(AbstractQueuedSynchronizer)
ReentrantLock
内部通过一个Sync
类来继承AQS
,实现锁的基本逻辑。Sync
有两个具体的实现类,NonfairSync
(非公平锁)和FairSync
(公平锁)。
以下是ReentrantLock
的基本使用代码示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
// 线程1尝试获取锁并执行任务
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("线程1获取到锁,执行任务...");
// 模拟任务执行
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
// 线程2尝试获取锁并执行任务
Thread thread2 = new Thread(() -> {
lock.lock();
try {
System.out.println("线程2获取到锁,执行任务...");
} finally {
lock.unlock();
}
});
thread1.start();
// 稍微延迟启动线程2,模拟并发情况
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
-
获取锁的过程
- 在
NonfairSync
中,lock()
方法首先会通过CAS
(Compare and Swap)操作尝试设置AQS
的状态(state)为 1,如果成功,表示获取到锁,设置当前线程为锁的拥有者。如果CAS
失败,会调用acquire(1)
方法,该方法会再次尝试CAS
获取锁,如果还是失败,则将当前线程包装成Node
节点加入到AQS
的等待队列中。 - 在
FairSync
中,获取锁的过程类似,但在lock()
方法中会先检查等待队列中是否有前驱节点,如果有,则当前线程不会尝试获取锁,而是加入到等待队列末尾,保证了公平性。
- 在
-
释放锁的过程
当线程执行完任务,调用unlock()
方法时,ReentrantLock
会调用AQS
的release(1)
方法。在release
方法中,首先会尝试通过CAS
将state
减 1,如果state
变为 0,表示锁已经完全释放,此时会唤醒等待队列中的头节点线程。
三、前端展示(使用 Vue)
我们可以创建一个简单的 Vue 组件来模拟ReentrantLock
的获取和释放情况:
<template>
<div>
<h2>ReentrantLock 演示</h2>
<button @click="acquireLock">线程1获取锁</button>
<button @click="acquireLock2">线程2获取锁</button>
<button @click="releaseLock" :disabled="!lockHeld">释放锁</button>
</div>
</template>
<script>
import ReentrantLock from './ReentrantLock.js'; // 假设这里是后端定义的 ReentrantLock 类的导入路径
export default {
data() {
return {
reentrantLock: new ReentrantLock(),
lockHeld: false
};
},
methods: {
acquireLock() {
try {
this.reentrantLock.lock();
this.lockHeld = true;
console.log('线程1获取到锁');
} catch (error) {
console.error('线程1获取锁失败', error);
}
},
acquireLock2() {
try {
this.reentrantLock.lock();
this.lockHeld = true;
console.log('线程2获取到锁');
} catch (error) {
console.error('线程2获取锁失败', error);
}
},
releaseLock() {
try {
this.reentrantLock.unlock();
this.lockHeld = false;
console.log('锁已释放');
} catch (error) {
console.error('释放锁失败', error);
}
}
}
};
</script>
<style>
/* 样式代码 */
</style>
四、ReentrantLock
的应用场景
- 在需要更灵活的锁控制的多线程环境中,
ReentrantLock
可以替代synchronized
关键字。例如,在一个多线程的资源访问场景中,如果某个线程获取锁后可能需要长时间执行任务,使用ReentrantLock
可以更好地控制其他线程的等待和唤醒。 - 当需要实现公平锁机制时,
FairSync
实现的ReentrantLock
可以保证线程按照请求锁的顺序依次获取锁,避免了饥饿现象。
总之,理解ReentrantLock
的实现原理对于编写高效、可靠的并发程序非常重要,它为我们在处理复杂的多线程同步问题时提供了更多的选择和控制手段。