ReentrantLock学习笔记
前言
java中提供了2种锁,一种是synchronized(内置锁,轻量级锁,重量级锁,偏向锁都是指的这种锁),另外一种是ReentrantLock,也就是我们常说的可重入锁。synchronized锁的是对象,而ReentrantLock锁的是线程,ReentrantLock更为复杂,锁的粒度比较细,使用不当,例如lock了,但是没有使用unlock释放锁,容易产生死锁问题。本文主要讲述ReentrantLock。
ReentrantLock
描述
ReentrantLock支持2种锁模式,公平锁和非公平锁,默认的是非公平锁(减少了线程切换,效率会更高一些),具体使用公平锁还是非公平锁,在ReentrantLock构造的时候,用户可以控制。
ReentrantLock的结构和类图:
sync是ReentrantLock的同步器,继承AbstractQueuedSynchronizer,结构见下:
ReentrantLock使用AbstractQueuedSynchronizer实现了同步队列的管理,具体的同步操作如加锁,解锁,通过调用同步器方法来实现,加锁和解锁是的一些业务逻辑,如可重入的处理,非公平锁的获取锁的操作处理等有自己定义的NonfairSync和FairSync来处理。
初始化
ReentrantLock的初始化,默认为非公平锁,用户可以根据需求自行定义锁的模式。
public ReentrantLock() {
//默认是非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
//用户根据需求设置
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁
非公平锁顾名思义不是公平的,公平的情况是,一个线程在获取锁的时候,需要进入队列中进行排队,FIFO,先到先得,按顺序处理,非公平的情况是,线程在获取锁的时候,进入队列排队之前,可以尝试获取锁,如果能获取到,就获得执行的机会,而不用顾忌队列中已经排队的线程,也就是说,a持有锁,队列中已经有的线程b,c,d,当前e尝试获取锁,如果这个时间点刚好a释放了锁,state为0,e刚好通过CAS,将锁独占,获得了锁,这是b,c,d还要继续在队列中等待锁的释放,对b,c,d线程来说,是不公平的。
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
nonfairTryAcquire,非公平锁模式下锁的尝试获取
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步状态,如果为0,就会尝试获取
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//获得锁,将独占线程,设置为自己,然后正常返回
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//此处就是ReentrantLock的可重入锁的机制,如果独占线程是自己,state累加,这里要注意,虽然锁可重入,但是占有锁的线程多次获取锁,就一定要多次释放锁,获取锁的操作和释放锁的操作要一一对应,保证state能归0,避免死锁
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁
公平锁在上一章节已经进行了描述,这里不做详述,看一下公平锁的实现。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
锁的释放
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
//释放锁后,判断队列中的head节点,如果合法,就唤醒它,去尝试获取锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
protected final boolean tryRelease(int releases) {
//递减state变量,主要用来处理可重入锁产生多state变量递增,state为0,表示锁已经释放,可以被其他线程获取
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;
}
其实在ReentrantLock的实现中,最重要的部分就是AQS,AQS同步器通过无锁的方式,基于CAS+队列的方式,提供了一种同步方案,AQS实现机制还是比较复杂的,后续有时间在深入源码分析一下