ReentrantLock(重入锁),利用AQS实现的一种可重入锁,和synchronized功能类似,但是synchronized有的它都有,synchronized没有的它还有。
实现原理
重入性
线程获取锁的时候,如果已经获取锁的线程就是当前线程的话,则此线程直接再次获取成功。由于锁会被获取n次,则在释放锁的时候也要释放n次,才算释放成功。
公平锁和非公平锁
ReentrantLock在构造函数中提供了是否公平锁的初始化方法,默认是非公平锁。非公平锁的效率要远远高于公平锁,一般我们没有特殊需求的话都是用非公平锁。
源码解析
ReentrantLock继承接口Lock,内部类继承AQS来实现锁的获取与释放。下面看一下源码是怎么实现获取锁和释放锁的,(下面代码是我截取的一些重要方法,加了一点注释)
public class ReentrantLock implements Lock, java.io.Serializable {
/**继承AQS*/
abstract static class Sync extends AbstractQueuedSynchronizer {
/**lock()方法需要子类实现*/
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;
}
}
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;
}
}
/**非公平锁*/
static final class NonfairSync extends Sync {
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 {
final void lock() {
acquire(1);
}
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 ReentrantLock() {
sync = new NonfairSync();
}
/**有参构造方法,fair决定是否是公平锁*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
/**外部调用的加锁方法*/
public void lock() {
sync.lock();
}
}
当程序中使用 new ReentrantLock()时,构造出非公平锁,调用lock方法时,会走NonfairSync类中的lock方法,然后CAS设置state值,如果成功则将设置当前线程拥有此独占锁(ReentrantLock锁是独占锁,还有共享锁,后面有文章会介绍),如果失败调用acquire(1),此方法里会调用下面的tryAcquire(int acquires)方法,这个方法接着会走Sync里的nonfairTryAcquire(int acquires)方法,
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
先获取state,如果是0,说明没有其他线程获取锁,则设置当前线程获取锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
如果state不是0,说明此锁已经被获取,再判断持有锁的线程是否是当前线程,如果是,则再次获取此锁(重入锁),state也会累加。
下面看一下释放锁,开发中我们会调用ReentrantLock的unlock方法,unlock中调用release,此方法最终会调用tryRelease方法
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;
}
首先state递减得到c,判断当前线程是否是持有锁的线程,如果不是则不能释放锁,抛出异常;接着判断c是否等于0,如果等于0,说明锁已经全部释放,同时清空持有锁的线程,如果不等于0,只是设置state新值,下次释放锁接着递减。
回到上面提到的,如果使用 new ReentrantLock(true)构造ReentrantLock时,创建的是公平锁;公平锁获取锁和非公平锁基本一样,只是多了一点校验
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
在争抢锁的时候,会多一个校验 hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
Node是AQS中维护的等待队列,此方法是查看等待队列中是否有比当前线程更早的等待线程。公平锁只会让等待队列中最早进来的线程获取锁,每次都要校验,因此公平锁的效率会远远低于非公平锁。
结论
ReentrantLock相对于synchronized来说,有很多好处,这里只说了一点支持公平锁,还有其他的比如可响应中断、可轮询请求等避免死锁的方法。但是synchronized是可以被jvm自动解锁的,不需要开发人员手动解锁,而ReentrantLock则需要手动解锁,因此为了避免程序出现异常造成无法解锁的情况,需要在finally中进行解锁操作。
共同学习,写下你的评论
评论加载中...
作者其他优质文章