AQS (3) ReentrantLock 公平锁与非公平锁

罗政:AQS (1) ReentrantLock 的上锁 与 解锁 源码

上面这篇文章里面,我讲的是 ReentrantLock 的 非公平锁( NonfairSync )

源码对比体现

lock 加锁方法的不同

//--------------NonfairSync的lock
final void lock() {
            //调用AQS的compareAndSetState ,cas判断state是不是0,是的话返回true,且把state改为1
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
  }
//--------------FairSync 的lock
 final void lock() {
      acquire(1);
 }

NonfairSync lock会先判断下是否有人占锁,没有的话直接拿锁成功,否则执行 acquire 抢占锁。

acquire 三步曲:

  • tryAcquire :试着拿锁,拿不到锁时进行下面步骤,拿到锁就把state加加,ExclusiveOwnerThread 线程设为自己。
  • addWaiter:将当前线程节点添加到AQS队列尾部(注意第一个节点并不是等待节点,而是一个空的头节点)。
  • acquireQueued: 拿不到锁时,将空的头结点的waitStatus改为SIGNAL信号(代表空的头结点的next需要等待拿锁),park当前线程。另一个线程解锁时,park唤醒。会回收空的头结点的next,将空的头结点的waitStatus改为0, 复原操作。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire

// ----------FairSync-----------       
 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;
        }
    // AQS队列中有等待节点,且等待的节点不是当前线程 则返回true
    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

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

如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

其余步骤两者完全一样!!!!!!!!!


解锁 过程,两者也是完全一样,但我在这里也讲下大致解锁过程。

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head; // 空的头结点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
        protected final boolean tryRelease(int releases) {
              // state 锁 减减 , 重入次数大于1时  state就不为0
            int c = getState() - releases;
           // 能解锁的线程不是AQS独占运行线程不能解锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
               //  state 已经为0 了,锁完全释放
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

release 三步曲

tryRelease : 试着解锁, 如果是重入锁的话,只把state减减,并没有解锁成功,返回false,否则state减为0,解锁成功,返回true。 解锁成功会执行下面操作。

unparkSuccessor : 唤醒的是空的头结点的next节点。并把空的头结点的waitStatus从SIGNAL改为0。

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

总结

公平锁和非公平锁只有两处不同:

  • 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
  • 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

接地气的总结

公平锁与非公平锁唤醒时都是取队列第一个空的头节点的next线程等待节点唤醒,但是此时如果有其他的线程正好也抢锁,非公平的做法就是立马有机会cas操作占锁,而公平锁的做法就是乖乖排到队尾等待。

强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者