1. 简介
ReentrantLock
,可重入锁,是一种递归无阻塞的同步机制。它可以等同于synchronized
的使用,但是ReentrantLock
提供了比synchronized
更强大、灵活的锁机制,可以减少死锁发生的概率。
ReentrantLock
还提供了公平锁和非公平锁的选择,构造方法接受一个可选的公平参数(默认非公平锁),当设置为true
时,表示公平锁,否则为非公平锁。公平锁与非公平锁的区别在于公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
// 采用公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 采用非公平锁
ReentrantLock unFairLock = new ReentrantLock();
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public ReentrantLock() {
sync = new NonfairSync();
}
2. 获取锁
我们一般都是这么使用ReentrantLock
获取锁的:
//非公平锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock()
方法:
public void lock() {
sync.lock();
}
Sync
为ReentrantLock
里面的一个内部类,它继承AQS
(AbstractQueuedSynchronizer
),它有两个子类:公平锁FairSync
和非公平锁NonfairSync
。
2.1 非公平锁
final void lock() {
// 首先尝试通过 CAS 的方式获取锁
if (compareAndSetState(0, 1))
// 获取成功后,将锁的拥有者设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取失败,调用AQS的acquire(int arg)方法
acquire(1);
}
首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)
方法,该方法定义在AQS
中,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法首先调用tryAcquire(int arg)
方法,在AQS
中,tryAcquire(int arg)
需要自定义同步组件提供实现,非公平锁实现如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取同步状态
int c = getState();
// state == 0,表示该锁未被获取
if (c == 0) {
// 通过 CAS 获取锁成功,设置为当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//线程重入
//判断锁持有的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
// 锁的重数++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2.2 公平锁
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;
}
}
2.3 区别
公平锁与非公平锁的区别在于获取锁的时候是否按照FIFO的顺序来。
比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
,定义如下:
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾节点
Node h = head; //头节点
Node s;
//头节点 != 尾节点
//同步队列第一个节点不为null
//当前线程是同步队列第一个节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
3. 释放锁
获取同步锁后,使用完毕则需要释放锁,ReentrantLock
提供了unlock
释放锁:
public void unlock() {
sync.release(1);
}
unlock
内部使用Sync
的release(int arg)
释放锁,release(int arg)
是在AQS
中定义的:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
与获取同步状态的acquire(int arg)
方法相似,释放同步状态的tryRelease(int arg)
同样是需要自定义同步组件自己实现:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
4. ReentrantLock与synchronized的区别
- 与
synchronized
相比,ReentrantLock
提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。 ReentrantLock
还提供了条件Condition
,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock
更加适合。ReentrantLock
提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized
则一旦进入锁请求要么成功要么阻塞,所以相比synchronized
而言,ReentrantLock
会不容易产生死锁些。ReentrantLock
支持更加灵活的同步代码块,但是使用synchronized
时,只能在同一个synchronized
块结构中获取和释放。注:ReentrantLock
的锁释放一定要在finally
中处理,否则可能会产生严重的后果。ReentrantLock
支持中断处理,且性能较synchronized
会好些。