AQS原理

AQS全称AbstractQueuedSynchronizer抽象队列同步器,它提供了一个FIFO的队列用于实现同步锁以及其他涉及同步锁的核心组件(例如JUC下的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等)

同步器主要支持实现

  1. 独占锁,只有一个线程访问资源(ReentrantLock)
  2. 共享锁,多个线程访问共享资源(ReentrantReadWriteLock)

AQS类图

在这里插入图片描述

AQS内部原理

AQS内部维护一个FIFO双向队列,每一个节点Node封装的是竞争锁失败的线程以及线程等待的状态信息
在这里插入图片描述

双向链表特点:
可以从任意元素开始访问前驱和后继

AQS队列变化的过程

  1. 添加Node
    如果当前线程竞争锁失败,则加入队列(进入锁池)

在这里插入图片描述

  • 将新的Node加入队尾,原队列最后的一个元素next指向新元素,新Node的prev指向原队列最后的一个元素,然后新元素的next指向自己
  • 通过CAS算法将tail指向新的Node(调用compareAndSetState方法)
  1. 释放Node
    当持有对象锁的线程释放机锁,就会从队列中取出第一个Node对象(释放第一个阻塞的线程)
    在这里插入图片描述
  • 将AQS的head指向下一个节点
  • 将新的节点prev更新为null

这个设置head的节点由获得锁的线程完成(也就是原第一个Node节点封装的线程),所以不需要用CAS算法设置head

AQS类使用说明

AQS是基于模板方法设计模式设计的,使用者需要继承同步器并重写指定的方法

重写时需要访问或者修改同步状态的方法
getState():获取当前同步状态。
setState(int newState):设置当前同步状态。
compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性

** 同步器可被重写的方法如下**
在这里插入图片描述
独占锁实现实例

public class Mutex implements Lock {
    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        // 当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        // 释放锁,将状态设置为0
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new
                    IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        // 返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition() { return new ConditionObject(); }
    }
    // 仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();
    public void lock() { sync.acquire(1); }
    public boolean tryLock() { return sync.tryAcquire(1); }
    public void unlock() { sync.release(1); }
    public Condition newCondition() { return sync.newCondition(); }
    public boolean isLocked() { return sync.isHeldExclusively(); }
    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
        
}

除了部分方法重写之外,其余的方法全部委派给静态内部Sync类处理

AQS源码(独占式同步状态获取与释放)

在这里插入图片描述
Sync继承自AQS,它有两个实现类分别是:

  • 公平锁(FairSync)所有线程按照FIFO顺序严格执行
  • 非公平锁(FairSync)CLH队列中的线程和新线程都有机会获取锁
sync.lock();

默认调用的是非公平锁

NonFairLock,lock()

final void lock(){
	if(compareAndSetState(0,1))//通过CAS操作修改对象锁state状态
		setExclusiveOwnerThread(Thread.currentThread());//修改状态成功,设置获得锁的线程
	else
		acquire(1);//获取锁失败,尝试加入阻塞队列(尝试获取锁)
}

compareAndSetState()
底层调用的是sun.misc.Unsafe类的compareAndSwapInt方法,原理是CAS算法判断比较互斥量

  • state=0,表示无锁
  • state>0,表示有锁,由于锁可重入,同一个线程多次获取同步锁会使state递增(state=3),释放锁的时候,同样也需要释放多次(将state=3递减成为state=0)

acquire()
这个方法是AQS的方法,线程执行到这里,表示state!=0
在这里插入图片描述

  • 如果tryAcquire尝试获取独占锁失败,那么就会调用addWaiter将线程封装成Node添加进AQS维护的CLH队列当中
  • acquireQueued将Node作为参数,自旋获取锁

tryAcquire()在NonfairSync中的实现
在这里插入图片描述

addWaiter()和enq()
在这里插入图片描述

  • 将当前线程封装为Node对象,添加入CLH队列
  • 判断tail即CLH队尾是否为null,不为null则加入队尾并将tail指针指向新的Node
  • tail为空说明队列为空,调用enq方法
  • enq自旋判断队列队尾是否为Null,如果为null则添加一个空的Node节点为头节点
  • 将新的Node节点前驱指向空的Node头,然后CAS比较将新的Node加入队尾

acquireQueued
在这里插入图片描述

  • 当前线程节点的prev节点是否为head,如果是则将当前Node设置为head,并覆盖移除初始化的head节点
  • 如果获取失败则根据waitStatus决定是否挂起,最后取消获取锁的操作

shouldParkAfterFaileedAcquire()
这个方法主要用于判断当前竞争锁失败的线程是否应该被阻塞,只有队列第二个Node可以竞争锁,对于其他节点应该继续阻塞
在这里插入图片描述

parkAndCheckInterrupt
如果判断当前线程应该被阻塞(返回true)时,就调用本方法,通过掉用LockSupport.park(和CAS操作类一样,也是来自于Unsafe)将线程挂起,里面只有俩方法
在这里插入图片描述
park()为获取一个permit,相当于消费一个许可
unpark()则是释放一个许可
有点类似Semaphore的许可,不同的是LockSupport里面的许可更贴近于互斥量(0/1),park调用时如果permit=0则方法会阻塞,unpark多次调用也不会改变许可的值

总结一下

AQS实现原理就是内部维护了一个CLH队列,将获取锁的线程封装成Node加入队列当中,初始化队列时会实例一个空的Node当作队头,每一次唤醒线程执行任务(运行线程释放了锁unlock->tryRelease())都会从第二个开始获取判断前驱是否是head,获取成功则把获取锁的线程设置为head,其余的Node节点一直处在自旋的状态中,阻塞节点时会判断队列是否为空,为空则先初始化,否则会直接将线程封装成Node添加至队尾,并用CAS算法将tail指向它
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值