关于AQS源码中公平锁的理解以及疑问

直入主题

可重入锁ReentrantLock中的实现方式有两种:公平锁和非公平锁,今天我们主要研究下公平锁

1、公平锁

调用lock.lock()时,会调用sync.lock(),具体来看下源码:

ReentrantLock:

public void lock() {
        sync.lock();
    }

FairSync:

final void lock() {
            acquire(1);
        }

AbstractQueuedSynchronizer:

public final void acquire(int arg) {
		// 首先在tryAcquire(1)中尝试获取锁
		// true:获取成功直接返回
		// false:获取失败,继续调用addWaiter(node)加入同步队列,入队后,执行acquireQueued(node,arg)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();

这里讨论的是公平锁的实现,因此进入公平锁的tryAcquire(acquires)

protected final boolean tryAcquire(int acquires) {
			// 首先获取当前线程
            final Thread current = Thread.currentThread();
            // 获取同步状态
            int c = getState();
            if (c == 0) {
            	// 同步状态为0(可以认为锁未被持有)
            	// 进入hasQueuedPredecessors()判断下是否需要排队 (我们重点分析的方法)
				// 第一种情况 高并发场景下,t1、t2、t3同时执行到此处
                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()

public final boolean hasQueuedPredecessors() {
        // 高并发场景下,t1、t2、t3同时执行到此处,首先CPU时间片切换至t1线程
        // 此时AQS队列尚未初始化,即head=tail=null,
        // 因此h!=t不成立(由于是短路与运算,h!=t不成立,所以&&后边的条件不会判断),
        // 于是hasQueuedPredecessors()返回false,继续执行compareAndSetState(0,1)成功,
        // 设置exclusiveOwnerThread为t1,t1获取到了锁
		// 时间片切换到t2,同样此时AQS队列尚未初始化,h!=t不成立,hasQueuedPredecessors()返回false,
		   继续执行casState(0,1),这时t2会执行失败(因为此时锁的拥有者是t1,t1还未释放锁),那么t2就会继续执行
		   addWaiter(node),这时AQS队尾tail是null,所以会执行enq()进行初始化队列
		Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
           		((s = h.next) == null || s.thread != Thread.currentThread());
    }

private Node enq(final Node node) {
     for (;;) {
         Node t = tail;
         if (t == null) { // Must initialize
             if (compareAndSetHead(new Node()))
                 tail = head;
                 // t2执行到这里时,AQS完成了初始化,此时head=tail且不为null,这时时间片切换到t3,继续进入t3线程的执行逻辑分析
         } else {
             node.prev = t;
             if (compareAndSetTail(t, node)) {
                 t.next = node;
                 return t;
             }
         }
     }
 }

public final boolean hasQueuedPredecessors() {
        // 时间片切换至t3,注意此时AQS队列的结构为head和tail同时指向一个虚拟节点(不为空),队列中只有一个
           节点,因为t2完成了队列的头尾初始化后,还没来得及加入队列,CPU进行了时间片切换至t3
        // 因此h!=t不成立,&&后边的条件不会执行,hasQueuedPredecessors()直接返回false
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

基于t3的状态对可能出现的两种情况进行分析:

此时t3处于什么样的状态呢?
t3已经从hasQueuedPredecessors()退出来了得到false,
取反后准备执行casState()的状态(等待执行的状态,具体情况见以下两种情况)

1、时间片切换为t1,t1释放了锁,state置为0,时间片切换为t3,t3尝试casState是可以成功的,也就占有了锁
问题来了!t3居然比t2抢先占有了锁,这不是非公平的吗?

这里我是这么理解的,现在AQS队列中的情况是头尾节点初始化好了(也仅仅是初始化好了头尾节点,指向同一个节点),但是t2线程所在的节点尚未加入队列(CPU时间片切换时,AQS队列初始化好了,但t2尚未加入队列中,处于游离状态,这个时候t2所在的节点不算排队),所以t3不算插队,结合方法名来看hasQueuedPredecessors()意思就是说,队列中是否有处于排队中的前驱节点,有的话就老老实实去排队,没有的话可以尝试加锁

2、时间片切换为t2,t2将AQS队列初始化后且加入了队列
(也就是此时队列中的节点数是2个,头:虚拟节点、尾:t2线程所在的节点),
就进入到acquireQueued(),t2线程所在的节点记为n2,其前驱节点为head,因此会继续执行tryAcquire(arg),继续执行hasQueuedPredecessors():
此时AQS队列的head!=tail成立,继续判断head.next==null(不成立),继续判断s.thread != Thread.currentThread()(false,此时老二节点中的线程就是t2),因此hasQueuedPredecessors()方法返回false,取反后准备执行casState(0,1),此时上下文切换到了t1,t1释放了锁,state=0,上下文切换至t3,t3继续执行tryAcquire(1)中的compareAndSetState(0, acquires),t3就可能casState(0,1)成功,再切换回t2,t2casState(0,1)就会失败,也就是获取锁失败
(为什么?不是公平锁吗?t2已经在队列里了,t3居然还能抢到锁,希望大佬们帮忙解答下,小码农在此谢过!)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值