概述
ReentrantLock 是 Java 并发中十分常用的一个类,具备类似 synchronized 锁的作用。但是相比 synchronized, 它具备更强的能力,同时支持公平锁和非公平锁。
公平锁: 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。
非公平锁: 多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队列的队尾等待。
那 ReentrantLock 中具体是怎么实现公平和非公锁的呢?它们之间又有什么优缺点呢?本文就带大家一探究竟。
RenentrantLock 原理概述
上面是 RenentrantLock 的类结构图。
-
RenentrantLock 实现了 Lock 接口,Lock 接口提供了锁的通用 api,比如加锁 lock,解锁 unlock 等操作。
-
RenentrantLock 底层加锁是通过 AQS 实现的,两个内部类 FairSync 服务于公平锁,NofaireSync 服务于非公平锁的实现,他们统一继承自 AQS。
ReentrantLock 类 API:
-
public void lock():获得锁
如果锁没有被另一个线程占用,则将锁定计数设置为 1
如果当前线程已经保持锁定,则保持计数增加 1
如果锁被另一个线程保持,则当前线程被禁用线程调度,并且在锁定已被获取之前处于休眠状态
-
public void unlock():尝试释放锁
如果当前线程是该锁的持有者,则保持计数递减
如果保持计数现在为零,则锁定被释放
如果当前线程不是该锁的持有者,则抛出异常
非公平锁实现
演示
@Test
public void testUnfairLock() throws InterruptedException {
// 无参构造函数,默认创建非公平锁模式
ReentrantLock reentrantLock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
new Thread(() -> {
reentrantLock.lock();
try {
System.out.println("线程" + threadNum + "获取锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// finally中解锁
reentrantLock.unlock();
System.out.println("线程" + threadNum +"释放锁");
}
}).start();
Thread.sleep(999);
}
Thread.sleep(100000);
}
运行结果:
线程0获取锁
线程0释放锁
线程1获取锁
线程1释放锁
线程3获取锁
线程3释放锁
线程2获取锁
线程2释放锁
线程5获取锁
线程5释放锁
线程4获取锁
线程4释放锁
....
-
默认构造函数创建的是非公平锁
-
运行结果可以看到线程 3 优先于线程 2 获取锁(这个结果是人为造的,很难模拟出来)。
加锁原理
-
构造函数创建锁对象
public ReentrantLock() {
sync = new NonfairSync();
}
-
默认构造函数,创建了 NonfairSync,非公平锁同步器,它是继承自 AQS.
-
第一个线程加锁时,不存在竞争,如下图:
// ReentrantLock.NonfairSync#lock
final void lock() {
// 用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示【获得了独占锁】
if (compareAndSetState(0, 1))
// 设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//失败进入
}
-
cas 修改 state 从 0 到 1,获取锁
-
设置锁对象的线程为当前线程
-
第二个线程申请加锁时,出现锁竞争,如下图:
-
Thread-1 执行,CAS 尝试将 state 由 0 改为 1,结果失败(第一次),进入 acquire 逻辑
// AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
// tryAcquire 尝试获取锁失败时, 会调用 addWaiter 将当前线程封装成node入队,acquireQueued 阻塞当前线程,
// acquireQueued 返回 true 表示挂起过程中线程被