【回顾一下AQS知识,关于公平锁与非公平锁】

文章目录

    • 一.什么是AQS
    • 二.公平锁和非公平锁实现
    • 三.公平锁和非公平锁的区别
    • 四.小结

一.什么是AQS

AQS,全称 AbstractQueuedSynchronizer,是 Java 中用于构建锁和同步器的一个基础框架类,位于 java.util.concurrent.locks 包中。AQS 通过一个先进先出的(FIFO)等待队列来管理线程之间的同步,能够简化自定义同步器的开发。

AQS 的核心思想是通过一个整型状态变量(state)表示共享资源的状态,来控制线程的获取与释放。
当state>0时表示已经获取了锁,
当state = 0时表示释放了锁,在实现重入锁时,
state > 1 通常表示当前线程已经多次获取了同一个锁,即锁的重入次数。
利用一个 FIFO 的双向链表来管理被阻塞的线程,当一个线程试图获取锁时,如果锁被占用,则该线程会被加入到等待队列中,当锁被释放时,会按照先进先出的顺序唤醒队列中的下一个线程,以重新尝试获取锁。

AQS 支持独占模式与共享模式
独占模式(exclusive):一个线程独占资源,其他线程必须等待。例如 ReentrantLock 就是这种模式。
共享模式(shared):多个线程可以同时访问资源,同一时刻可以有多个线程获取同步状态。例如 CountDownLatch 使用共享模式。

如图所示:
在这里插入图片描述

二.公平锁和非公平锁实现

这里只分析ReentrantLock中公平锁和非公平锁的源码实现,其余暂不做拓展了。

瞅瞅源码:

public class ReentrantLock implements Lock, java.io.Serializable {

    // ReentrantLock的构造方法,传入布尔值可以构造公平锁对象 还是 非公平锁对象
  	public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    // ReentrantLock的加锁方法
	 public void lock() {
        sync.lock();
    }

    // 成员变量Sync锁对象
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
    
        abstract void lock();
        
        /**
	     * 非公平锁的尝试去获取锁方法
	     */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 拿到同步状态,如果资源刚好是空闲的情况下:不管队列里面有没有线程在排队,直接来一次CAS抢锁操作,抢到就执行
            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;
        }
}

   /**
    * 非公平锁,继承这个抽象静态内部类Sync
    */
    static final class NonfairSync extends Sync {
        final void lock() {
            // cas加一次锁
            if (compareAndSetState(0, 1))
                //设置当前为独占访问的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
               // cas没抢到锁则执行acquire方法,这个方法来自Sync->AbstractQueuedSynchronizer的acquire()方法
                acquire(1);
        }

        // 非公平锁子类实现尝试去获取锁方法--重写父类的tryAcquire方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

   /**
    * 公平锁,同样继承这个抽象静态内部类Sync
    */
    static final class FairSync extends Sync {

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

       /**
	     * 公平锁子类实现尝试去获取锁方法--重写父类的tryAcquire方法
	     */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果共享资源状态是空闲
            if (c == 0) {
                // hasQueuedPredecessors方法来自Sync->AbstractQueuedSynchronizer的hasQueuedPredecessors()方法,如果头节点下一个节点就是当前线程,即没有前驱节点的情况下,进行一次CAS抢锁操作
                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里面的相关方法:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{

/**
 * Node 类主要用于管理线程的排队等待信息,AQS 中的等待队列是一个双向链表,这些节点串联成队列,表示线程在获取锁或者其他同步资源时的排队情况。
 * Node 保存了线程的状态、排队信息以及对前驱和后继节点的引用。
*/
static final class Node {

    // 共享模式的标志节点:SHARED, 用于标识当前节点是以 共享模式 进行同步操作(例如:CountDownLatch、Semaphore)。
    static final Node SHARED = new Node(); 
    
    // 独占模式的标志节点:EXCLUSIVE,用于标识当前节点是以 独占模式 进行同步操作(例如:ReentrantLock)。
    static final Node EXCLUSIVE = null;     

    // 线程被取消状态:线程在等待过程中被中断或超时,取消状态的节点不会再参与队列调度。
    static final int CANCELLED = 1;
    
    // 后继线程需要被唤醒状态: 当前节点释放锁或者资源后,应该唤醒它的后继节点。
    static final int SIGNAL = -1;
    
    // 线程正在等待条件队列:表示当前节点正在等待某个条件(用于 Condition 的实现)。当其他线程调用 signal() 时,处于该状态的节点会从条件队列转移到同步队列中。
    static final int CONDITION = -2;
    
    // 表示下一次获取共享资源时,可以进行传播唤醒其他节点。这通常在 共享模式 下使用。
    static final int PROPAGATE = -3;

    // 当前节点的等待状态
    volatile int waitStatus;

    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;

    // 当前节点的线程,也就是当前等待获取锁或者资源的线程。
    volatile Thread thread;

    //这个成员变量用于 Condition 队列中,用来保存条件等待队列中的下一个节点。与同步队列不同,Condition 是一个单向队列。
    Node nextWaiter;
}

 //......

 /**
  * 注意这是个短路与, !tryAcquire(arg):尝试去获取锁成功了,则不走后面的添加到等待队列的逻辑。
  */
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
}

// AQS里面的tryAcquire是空方法,待子类去重写的,及会调用到
 protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }


/**
 * 判断当前线程是否有前驱线程,也就是判断当前线程是否应该排队等待
 */
public final boolean hasQueuedPredecessors() {
      Node t = tail; 
      Node h = head;
      Node s;
      // 只要头节点的下一个节点不是当前线程所在的节点,返回 true,表示有前驱节点
      return h != t &&
          ((s = h.next) == null || s.thread != Thread.currentThread());
  }

从上面源码分别对公平锁、非公平锁分析。

  • 公平锁:ReentrantLock fairLock = new ReentrantLock(true);
  • fairLock.lock();
  1. lock()方法:只调用acquire(),其余不做任何事
  2. 公平锁的tryAcquire方法里面:如果共享资源状态是空闲(state==0)情况下,则看有没有前驱线程节点在排队,如果没有在排队的?CAS抢锁 :尾部排队(加入到双向链表尾部)
  • 非公平锁:ReentrantLock noFairLock = new ReentrantLock(false);
  • noFairLock.lock();
    1.lock()方法:上来就cas加一次锁,抢到则占有。
    2.在第一次没抢到,则调用非公平锁的tryAcquire方法:拿到同步状态,如果资源刚好是空闲(state==0)的情况下,不管队列里面有没有线程在排队,直接来一次CAS抢锁操作,抢到就执行。
    3.非公平锁经过前面两次如果都没抢到锁,那么也要尾部排队了(加入到双向链表尾部)

三.公平锁和非公平锁的区别

  1. 非公平锁在lock方法会CAS抢一次锁,公平锁不会;
  2. 非公平锁在tryAcquire方法里面,如果资源刚好空闲则不管队列有没有前驱节点在排队,直接抢锁;而公平锁在tryAcquire方法里面,如果资源刚好空闲则看有没有前驱节点在排队,有则排到队尾,没有则进行CAS抢锁操作;

四.小结

之前有认真学过一遍AQS这块源码,很久没看有点生疏了,写篇博客回顾一下~

上一篇:Acrel-1200——分布式光伏运维云平台


下一篇:jeecg3版本的vue,离线启动