并发ReentrantLock中公平锁和非公平锁的理解
Posted 赵晓东-Nastu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发ReentrantLock中公平锁和非公平锁的理解相关的知识,希望对你有一定的参考价值。
【前言】
ReentrantLock 是在JavaSE5之后,并发包中新增了Lock接口用来实现锁功能,它提供了与synchronized关键字类似的同步功能。同时ReentrantLock也是重入锁,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁,该锁还支持获取锁时的公平和非公平性选择。
【公平锁】
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
从代码中可以看出FairSync继承了Sync实现了同步器,也就是我们所说的AQS,如果我们调用lock方法,也就是我们调用了acquire的方法并且传入的参数为1
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先我们进入tryAcquire方法
protected final boolean tryAcquire(int acquires) {
// 获取当前的线程
final Thread current = Thread.currentThread();
//获取状态
int c = getState();
//判断状态是否为0 状态为0表示没有加锁
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;
}
}
进入这个线程我们首先获取当前线程,然后判断状态是否为加锁状态,如果是没有进行加锁的状态,则进入到hasQueuedPredecessors方法判断队列是否有值,如果没有值,则进行加锁,如果队列中有值,tryAcquire则返回false
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
!tryAcquire为true ,并且进入到了addWaiter方法中
// 将该线程new到一个节点里面
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//将tail赋值给pred
Node pred = tail;
//如果tail 不为null
if (pred != null) {
//那么就将新new出来结点的前指针指向pred
node.prev = pred;
//将新new的节点设置为尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果尾结点为空节点
if (t == null) { // Must initialize
//直接new一个新的节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
进入到这个方法中是进入入队的操作
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//判断前一个节点是否为头结点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
首先我们判断前一个节点是否为头结点,如果为头结点我们进入到了tryAcquire方法中继续进行获取锁,如果获取锁成功了,
那么设置结点为头结点。
如果该结点的上一个结点不是头结点,那么执行下面的代码
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
shouldParkAfterFailedAcquire 方法主要逻辑是使用compareAndSetWaitStatus(pred,ws,node.SIGNAL)使用CAS将节点状态由 INITIAL 设置成 SIGNAL,表示当前线程阻塞。当 compareAndSetWaitStatus 设置失败则说明 shouldParkAfterFailedAcquire 方法返回 false,然后会在 acquireQueued 方法中死循环中会继续重试,直至compareAndSetWaitStatus 设置节点状态位为 SIGNAL 时 shouldParkAfterFailedAcquire 返回 true 时才会执行方法 parkAndCheckInterrupt 方法。
parkAndCheckInterrupt 该方法的关键是会调用 LookSupport.park 方法(关于LookSupport会在以后的文章进行讨论),该方法是用来阻塞当前线程。
如果获取锁失败的话,先将节点状态设置成SIGNAL,然后调用LookSupport.park方法使得当前线程阻塞。
【非公平锁】
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平锁上来就进行获取锁
如果获取锁失败了,接下来走acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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;
}
如果状态为0,则直接进行获取锁,其他的逻辑判断一样。
【公平锁和非公平锁的对比】
非公平锁会进行两次的强锁
非公平锁的效率要高
ReentrantLock默认为非公平锁
以上是关于并发ReentrantLock中公平锁和非公平锁的理解的主要内容,如果未能解决你的问题,请参考以下文章
java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁
深入了解ReentrantLock中的公平锁和非公平锁的加锁机制