图解AQS原理之ReentrantLock详解-公平锁

概述

前面已经讲解了关于AQS的非公平锁模式,关于NonfairSync非公平锁,内部其实告诉我们谁先争抢到锁谁就先获得资源,下面就来分析一下公平锁FairSync内部是如何实现公平的?如果没有看过非公平锁的先去了解下非公平锁,因为这篇文章前面不会讲太多内部结构,直接会对源码进行分析
前文连接地址:图解AQS原理之ReentrantLock详解-非公平锁

本文分析的JDK版本是1.8

温馨提示:读本文内容建议结合之前写的非公平,前篇设计了很多基础性内容

源码分析

在源码分析之前,我们先来看一下ReentrantLock如何切换获取锁的模式呢?其实是在构造器中传递指定的类型变量来控制使用锁的方式,如下所示:

1
2
3
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

fair参数指定为true时,代表的是公平锁,如果指定为false则使用的非公平,无参的构造函数默认使用的是非公平模式,如下所示:

1
2
3
public ReentrantLock() {
sync = new NonfairSync();
}

接下来我们以一个例子来进行后面的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ReentrantLockDemo {

public static void main(String[] args) throws Exception {
AddDemo runnalbeDemo = new AddDemo();
Thread thread = new Thread(runnalbeDemo::add);
thread.start();
Thread.sleep(500);
Thread thread1 = new Thread(runnalbeDemo::add);
thread1.start();
System.out.println(runnalbeDemo.getCount());
}

private static class AddDemo {
private final AtomicInteger count = new AtomicInteger();
private final ReentrantLock reentrantLock = new ReentrantLock(true);
private final Condition condition = reentrantLock.newCondition();

private void add() {
try {
reentrantLock.lockInterruptibly();
count.getAndIncrement();
} catch (Exception ex) {
System.out.println("线程被中断了");
} finally {
// reentrantLock.unlock();
}
}

int getCount() {
return count.get();
}
}
}

我们通过源码可以看到这里我们启动了两个线程,两个线程分别进行同步锁操作,这里我并没有释放掉锁,因为方便分析队列的情况,当然你也可以在内部写一个死循环,不释放锁就可以了,我这里简单的不释放锁,使用的是可中断的获取锁操作方法lockInterruptibly,这里内部的原理我们上一篇文章中已经讲解过了,这里并不过多的去分析内部原理,这个ReentrantLocklockInterruptibly调用内部类AQSacquireInterruptibly,但是其实是FairSync内部类继承了内部类Sync,而内部类Sync有继承了AbstractQueuedSynchronizer简称AQS,acquireInterruptibly源码信息如下所示:

1
2
3
4
5
6
7
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}

这里我们通过上一篇文章得知tryAcquire是需要子类去实现的方法,我们在例子中指定了使用的是公平锁,所以tryAcquire方法的实现是在ReentrentLockFairSync类中,我们来具体看一下这个方法,重点也在这个方法中其他的其实都是一样的,因为用的方法都会一样的非公平和公平锁的调用,唯独不一样的就是子类实现的方法是不相同的,接下来我们就来看一下公平锁的tryAcquire是如何实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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; //state递增
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //设置state状态
return true; //获得锁成功
}
return false; //获得锁失败
}

对比非公平锁的NonfairSync类的tryAcquire方法,其实就是在锁可用的情况下增加了一个判断条件,这个判断方法就是hasQueuedPredecessors,从方法的名称来看说的是有等待的线程队列,换句话说已经有人在排队了,新来的线程你就不能加塞,而非公平模式的谁先争抢到锁就是谁的,管你先来不先来,接下来我们具体看一下这个

hasQueuedPredecessors方法源码:

1
2
3
4
5
6
7
8
9
10
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // 获得尾节点
Node h = head; // 获得头节点
Node s;
return h != t && //头节点和尾节点相同代表队列为空
((s = h.next) == null || s.thread != Thread.currentThread()); //头节点的next节点为空代表头节点,以及s.thread不是当前线程不是自己的话代表队列中存在元素
}

通过上面的源码信息,可以得出其实内部主要就是判断有没有排队等待的节点,队列是否为空,如果为空的话则可以争抢锁,如果队列不为空,伙计你必须老老实实给我排队去,除非占有锁的线程和请求锁的线程是一样的,否则还是老老实实排队去,这就是公平模式的锁操作,还有一个lock方法,公平模式的lock方法,没有直接上来先获取锁,而是先尝试获得锁直接调用AQSaquire方法进行尝试获取锁,下面是FairSync源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1); //这里直接调用了aquire并没有尝试修改state状态
}

/**
* 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;
}
}

结束语

本内容主要是结合上篇内容的一个续篇,可以结合上篇然后再看下篇会比较清晰些。

文章目录
  1. 1. 概述
  2. 2. 源码分析
  3. 3. 结束语
|
载入天数...载入时分秒...