结合源码分析AQS实现原理

AQS即并发包java.util.concurrent.locks下的抽象类

并且AQS也是并发包的基础,并发包中的锁都是基于AQS实现的,而并发包中其它的类,如并发容器等都是利用并发包中的锁实现的。

在网上见到一个很好的图来展示并发包的架构:


此图最能说明并发包的实现架构。一切并发包都是基于volatile的语义和CAS乐观锁实现的,AQS也不例外。


1.什么是AQS?

     它维护了一个volatile int state(代表共享资源也称同步器)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。


CLH队列分析:

整体队列


AQS依赖同步队列(一个FIFO的双向队列,其实就是数据结构双向链表)来完成同步状态的管理。当前线程获取同步状态失败时,AQS会将当前线程以及等待状态等信息构造成一个节点(Node)并且将其加入到同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

同步队列中的Node节点用来保存获取同步状态失败的线程引用。等待状态以及前驱和后继节点。


   


Node是构成同步队列的基础,AQS拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会放入到队列的尾部

同步队列的基本结构:


同步器中包含了两个节点类型的引用,一个指向头节点(head),一个指向尾节点(tail)。,当一个线程成功的获取到锁(同步状态),其他线程无法获取到锁,而是被构造成节点(包含当前线程,等待状态)加入到同步队列中等待获取到锁的线程释放锁。这个没有获取到锁的线程,加入到队列的过程必须保证线程安全,否则如果多个线程的环境下,可能造成添加到队列等待的节点顺序错误,或者数量不对。因此同步器提供了一个基于CAS的设置尾节点的方法CompareAndSetTail(Node expect,Node update),它需要传递当前线程认为的尾节点和当前节点,只有设置成功后,当前节点才能正式与之前的尾节点建立关联。

同步器将节点加入到同步队列的过程如图所示: 
这里写图片描述

同步队列遵循FIFO,首节点是获取锁成功的节点,首节点的线程在释放锁时,将会唤醒后继节点,而后继节点将会在获取到锁时,将自己设置位首节点,过程如下所示: 
这里写图片描述

设置首节点是由成功获取锁的线程来完成的,由于只有一个线程能够成功获取锁,因此设置首节点不需要CAS操作。


state的访问方式有三种:

getState() 获取当前的同步状态
setState(int newState) 设置当前同步状态
compareAndSetState(int expect,int update) 使用CAS设置当前状态,该方法能够保证状态设置的原子性。
同步器可以支持独占式的获取同步状态,也可以支持共享式的获取同步状态,这样可以方便实现不同类型的同步组件。

 AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

  不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

  以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

  再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

  一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值