AQS底层原理及源码分析详解

一. AQS是什么

队列同步器AbstractQueuedSynchronizer(简称为AQS),是用来构建锁或者其他同步组件的基础框架,通过内置的FIFO(先来先服务)队列来完成资源获取线程的排队工作。AQS是实现锁的关键,简单理解两者的关系就是:锁是面向使用者的;AQS面向的是锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理,线程排队,等待唤醒底层操作的细节,对外放出模板方法供子类实现。

二. AQS底层原理

AQS内部用一个volatile修饰的int类型的成员变量state来控制同步状态。

  • state = 0:表示没有线程正在独占共享资源的锁。
  • state = 1:表示有线程正在共享资源的锁。

1. AQS类图(红色线是内部类)

AQS类图

  • AbstractOwnableSynchronizer:抽象类,定义了存储独占当前线程的属性和设置,获取当前线程的方法。
  • AbstractQueuenSynchronize:抽象类,AQS框架核心类,内部以虚拟队列的方式管理线程的锁获取与锁释放,其中获取锁(tryAcquire方法)和释放锁(tryRelease方法)并没有提供默认的实现,需要子类重写方法的具体逻辑,目的是为了使开发人可以自定义获取锁和释放锁的方式。
  • Node:AbstractQueuenSynchronize的内部类,用于构建虚拟队列(双向链表),为每个进入同步队列的线程封装成Node对象加入队列,管理需要获取锁的线程。
  • Sync:抽象类,是ReentrantLock的内部类,继承了AbstractQueuenSynchronize,实现了tryRelease方法,并提供抽象方法lock,供子类实现
  • NonfairSync:Reentrantlock的内部类,继承Sync,非公平锁的实现类
  • FairSync:Reentrantlock的内部类,继承Sync,公平锁的实现类
  • Reentrantlock:实现了Lock接口,创建时默认为非公平锁。

2. AQS类详解

AQS虽说是一个抽象类,但是其内部没有一个方法是抽象方法,因为AQS只是基础的组件,作者并不希望使用者对其直接进行操作,更倾向于其作为基础组件,为其实现类提供基础的帮助。

AQS采用的是模板方法模式,其内部除了提供并发的操作核心方法以及同步队列的操作之外,还提供了一些模板方法让子类自己实现,如加锁解锁。

AQS作为基础的组件,封装的都是核心的并发操作,实际上还分为两种模式,共享模式和独占模式,如Reentrantlock,ReentrantReadWriteLock(写锁部分)都是独占锁,ReentrantReadWriteLock(读锁部分)就是共享锁。
这两种模式的解锁和加锁逻辑都不一样,但是AQS只关注内部的公共方法的实现,不关心外部的具体实现,所以提供了模板方法给子类。

要实现独占模式,则需要实现tryAcquire(加锁)和tryRelease(解锁),而实现共享模式则需要实现tryAcquireShared(加锁)和tryReleaseShared(解锁),无论是共享模式还是独占模式,其底层实现都是同一个AQS,只是加锁和解锁逻辑不一样,所以,根据自己的需求自定义锁也就变得简单。

看看AQS提供的5个模板方法:
AQS提供的模板方法
AQS的作用基本有了概念,那么AQS到底怎么工作的呢?
先来看看它的内部类:Node
Node的属性

有的属性基本看注释就能看懂啥意思,让我们看看waitStatus的5个常数的含义

  • CANCELLED:值为1,在同步队列中等待超时或被中断,需要从同步队列中取消该Node的节点,其节点的waitStatus为CANCELLED,即结束状态,进入该状态后的节点将不会再变化。
  • SIGNAL:值为-1,被标识为该等待唤醒状态的后序结点,当其前序节点的线程释放了同步锁被取消,将会通知该节点的后序节点的线程执行。就是处于唤醒状态,只要前序节点释放锁,就会通知标识为SIGNAL状态的后序节点的线程执行。
  • CONDITION:值为-2,与Condition相关,该标识的节点处于等待队列中(后面会说)节点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的节点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识节点的线程处于可运行状态。
  • 0状态:值为0,代表初始状态

3. Condition(等待队列)

任何的一个对象,都有一组监视器方法(定义在Object上),主要包括wait,wait(long),notify,notifyAll方法,主要与synchronized同步关键字使用,可以实现等待通知的作用。同理,ConditionObject是AQS的内部类,因为Condition的操作需要先获取相关联的锁,所以在同步器的内部也合理。

等待队列是什么?

等待队列是一个FIFO的队列,每个节点都包含一个线程的引用,该线程就是在Condition对象中等待的线程,如果线程调用了await方法,线程将会加入等待线程,释放锁,并进入等待状态。节点都是AQS的静态内部类Node。

4. AQS工作详解

下图就是AQS底层模型图:
AQS工作总图

  • 当线程调用await方法(线程先进入队列,后释放锁):加入等待队列
  • 线程调用signal方法(移动到同步队列尾部,唤醒线程):加入同步队列

5. 源码分析

先看AQS底层分装的核心方法

为了方便理解,这边加入Reentrantlock子类实现代码

final void lock() {
	//cas操作设置state的值,如果state为0,则说明当前共享资源没有线程占用,设置为1,成功后设置独占线程为当前线程。
     if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
     else
     //如果state不为0,则说明有线程独占了,进入AQS核心方法,上图可见,随后进入tryAcquire方法
         acquire(1);
}

//这是子类具体实现方法实现锁的逻辑,进入nonfairTryAcquire方法
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

//acquires = 1
final boolean nonfairTryAcquire(int acquires) {
	//获取当前线程
    final Thread current = Thread.currentThread();
    //获取state状态
    int c = getState();
    //如果状态为0,跟之前一样,则进入cas设置,并将独占线程设置为自己,返回true
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果state不为0,且当前线程为独占线程,进行+1,也就是重入锁的实现。
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //如果都不满足,表示既有独占线程,当前线程又不是独占线程,则要进入同步队列,等待锁的释放。这一步完进入AQS核心代码也就是第一个图
    return false;
}
public final void acquire(int arg) {
	// 看完重入锁的加锁实现源码后,应该就了解了第一个方法的具体作用,arg = 1,tryAcquire提供给子类实现,具体实现看子类,都是操作state
	//当第一个方法放回false,!false为true的时候,表示线程需要进入同步队列,设置当前线程的状态为独占模式,也就是Node.EXCLUSIVE。进入addWaiter方法
      if (!tryAcquire(arg) &&
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
  }


private Node addWaiter(Node mode) {
	//将当前线程封装成Node节点。
   Node node = new Node(Thread.currentThread(), mode);
   // tail默认为null,如果tail不为空,说明同步队列已经有节点,因为Node pred = tail,则cas比满足条件,则将node节点加入队尾。
   Node pred = tail;
   if (pred != null) {
       node.prev = pred;
       if (compareAndSetTail(pred, node)) {
           pred.next = node;
           return node;
       }
   }
   //如果是第一个节点,则进入enq方法
   enq(node);
   return node;
}

private Node enq(final Node node) {
	//通过自旋的方式
	//该部分用图表示
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

enq方法图解:
第一次循环:
在这里插入图片描述
第二次循环:
在这里插入图片描述
完成对线程节点node的插入,可以看到,第一个节点是空节点,也被称为哨兵节点,具体作用我也不清楚(有大佬说的话更好)

方法结束后,再回去看acquire方法;

public final void acquire(int arg) {
      if (!tryAcquire(arg) &&
      	//根据上面分析,返回的是node,新插入节点,接着看acquireQueued方法
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
  }



final boolean acquireQueued(final Node node, int arg) {
//标记是否成功拿到资源
    boolean failed = true;
    try {
    //标记等待过程是否被中断
        boolean interrupted = false;
        //看到死循环就想到自旋
        for (;;) {
        //获取node的前序节点,这里目前是哨兵节点
            final Node p = node.predecessor();
            //如果前序节点是哨兵节点,则表示你是第二个,老二了,然后这里在尝试获取一次锁,如果成功
            if (p == head && tryAcquire(arg)) {
            //则将当前线程节点设置为哨兵节点
                setHead(node);
                //将之前的哨兵节点置成垃圾,方便gc
                p.next = null; // help GC
                //标识成功获取到资源
                failed = false;
                //表示线程未被中断
                return interrupted;
            }
            //shouldParkAfterFailedAcquire方法判断线程是否需要等待,如果需要则返回true
            //parkAndCheckInterrupt方法进行unsafe.park
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //都满足后将状态设置为true,也就是中断线程
                interrupted = true;
        }
    } finally {
    //如果节点得不到资源,timeout了则进行中断资源的获取
        if (failed)
            cancelAcquire(node);
    }
}

接着回到acquire

public final void acquire(int arg) {
   if (!tryAcquire(arg) &&
   	//根据上面分析,返回的是node,新插入节点,接着看acquireQueued方法
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       //如果返回acquireQueuedfalse,则表示已进入队列,或者获取到资源
       //返回true,则进入selfInterrupt方法,方法里面很简单
       selfInterrupt();
}

static void selfInterrupt() {
		//中断线程
        Thread.currentThread().interrupt();
    }

加锁搞定,下面是解锁,相对于加锁,解锁就太简单了

public void unlock() {
//进入release方法
   sync.release(1);
}

//AQS独占锁解锁核心代码
public final boolean release(int arg) {
//进入子类实现的解锁逻辑
//如果tryRelease返回true说明没有独占锁,否则好友线程持有
    if (tryRelease(arg)) {
    //当线程没有持有锁后,唤醒下一个线程执行
        Node h = head;
        //根据waitstatus判断需要唤醒哪个线程
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
} 	


//子类定义的解锁逻辑
protected final boolean tryRelease(int releases) {
//获取state - 1
    int c = getState() - releases;
    //判断当前线程是否是独占资源的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
        //设置资源是否还被独占着
    boolean free = false;
    //如果state = 0,表示没有线程独占
    if (c == 0) {
    //设置为true
        free = true;
        //当前独占线程置空
        setExclusiveOwnerThread(null);
    }
    //cas设置state
    setState(c);
    return free;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值