彻底理解ReentrantLock
java除了使用关键字
synchronized
外,还可以使用
ReentrantLock
实现独占锁的功能。而且
ReentrantLock
相比
synchronized
而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。这篇文章主要是从使用的角度来分析一下
ReentrantLock
。
简介
ReentrantLock
常常对比着synchronized
来分析,我们先对比着来看然后再一点一点分析。
synchronized
是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock
也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。synchronized
可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock
也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。synchronized
不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock
可以相应中断。
ReentrantLock
好像比synchronized
关键字没好太多,我们再去看看synchronized
所没有的,一个最主要的就是ReentrantLock
还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
源码解析
RetrantLock
有两种实现方式,一种是公平锁,一种是非公平锁,如字面意思,公平锁也就是在等待队列中等待时间越长的Thread
拥有优先获取锁的资格,而非公平锁则不一样,每一次有线程释放锁之后,哪个线程取得锁是不可预期的,但这个不代表着随机线程获取锁。可以从源码的角度来分析;
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 抽象方法,获取锁
abstract void lock();
//以非公平的方式获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//没有线程获取锁,直接获取锁,并设置为当前线程独占
if (c == 0) (
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程已经获取了锁,再次获取锁,执行+aquires操作
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;//获取锁失败
}
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;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
.......
}
Sync
类是ReentrantLock
的一个类,继承自AbstractQueuedSynchronizer
,用于管理获取锁和释放锁;Sync
实现了以非公平的方式。Sync
有两个子类,分别是NonfairSync
和FairSync
,我们来看源码:
/**
* 非公平锁,非公平锁中释放锁后,等待队列中获取锁的线程是不可预知的。但这不代表着随机。
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 如果state为0,获取锁,将当前线程设置为独占线程,否自进入循环队列,不断获取锁acquire(1)
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//以非公平的方式尝试获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//以公平的方式获取锁,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;
}
}
对比UnfairSync
和FairSync
的tryAcquire(int acquires)
方法,其中UnfairSync
会去做判断hasQueuedPrecessors()
,如果队列中还有前置节点,当前线程就不会获取锁,因此保证了先进入队列的线程会先获取锁,因此被称为公平锁。而非公平锁没有此判断,获取锁失败后,会进入队列,不断尝试获取锁。