AQS解析

AQS

谈到并发,不得不谈ReentrantLock,详情见上篇文章[https://blog.csdn.net/qq_28822933/article/details/83539208],而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!

AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch等,AQS采用的是模板方法模式。
AQS中有独占锁和共享锁,重入锁。

分析AQS中的方法结构

AQS中的模板方法:

	独占式获取:
		accquire()
		acquireInterruptibly()
		tryAcquireNanos()
	共享式获取:
		acquireShared()
		acquireSharedInterruptibly()
		tryAcquireSharedNanos()
	独占式释放锁:
		release()
	共享式释放锁:
		releaseShared()

需要子类覆盖的流程方法:

独占式获取:tryAcquire()
独占式释放:tryRelease()
共享式获取:tryAcquireShared()
共享式释放:tryReleaseShared()
这个同步器是否处于独占模式:isHeldExclusively()

同步状态state(标志的当前线程是否获取到锁):

getState():获取当前的同步状态
setState():设置当前同步状态,不能保证原子性
compareAndSetState():使用CAS设置同步状态,保证状态设置的原子性

独占锁,共享锁和重入锁

独占锁(类似于ReentrantLock和ReentrantReadWriteLock里的写锁都是独占锁):
定义一个state,如果是1就是拿到了锁,反之则没有,独占锁就是一个排它锁。
共享锁(类似于ReentrantReadWriteLock里的读锁是共享锁):
定义state是很多个锁的数量,拿到一个锁就state–,释放一个就state++,Semaphore的实现就是参照共享锁。
重入锁:
比如一个线程拿到一个锁,在run中又调用了其他同步方法,如果不能重入,一个线程就要拿到两个锁,这样就不行。

AQS的数据结构

AQS的数据结构–双向链表:
1,节点Node

	竞争失败的线程会打包成Node放到同步队列,Node可能的状态里:
		CANCELLED:线程等待超时或者被中断了,需要从队列中移走
		SIGNAL:后续的节点处于等待状态,当前节点被释放了,通知后面的节点去运行
		CONDITION:当前节点处于等待队列中
		PROPAGATE:共享,表示状态要往后面的节点传播
		0:表示初始状态

2,FIFO同步队列

AQS的原理就是:
当一个锁线程获取锁失败的时候,会将这个线程封装成一个节点Node,通过addWaiter方
法放入到FIFO同步队列中的尾部(这一步是试用CAS操作,保证原子性),然后再通过
acquireQueued方法自旋从同步队列的头部依次获取锁,如果其中某个获取锁失败,就继
续封装成一个Node放入队列的尾部,直到获取锁返回。

比如:当前有10个线程去竞争锁,有一个拿到了锁,另外9个就会被依次封装成一个Node节点,按照线程优先级依次从尾部放入到同步队列中,用节点的前驱和后驱连接起来行程双向链表,然后当获取锁的线程释放了锁之后,就会依次从头部去唤醒线程。

注意:
1,在增加尾节点的时候,是采用的CAS,因为在竞争锁的时候可能是多个线程,最后有多个线程失败需要进入到队列中的某个尾节点,所以需要CAS。
2,在删除头节点的时候,不需要采用CAS,因为头节点的线程就只有一个,不存在竞争。

AQS中ConditionObject的数据结构

ConditionObject的数据结构-单向链表:
实现原理:ConditionObject内部实现了一个FIFO(先进先出)等待队列,队列的每个节点都是等待在Condition对象上的线程的引用,在调用Condition的await()方法之后,线程释放锁,构造成相应的节点进入等待队列等待,其中节点的定义复用AQS的Node定义。插入节点只需要将原有尾节点的nextWaiter指向当前节点,并且更新尾节点。更新节点并没有像AQS更新同步队列使用CAS是因为调用await()方法的线程必定是获取了锁的线程,锁保证了操作的线程安全。

当调用await方法之后,会将同步队列中的线程放入到等待队列中,当调用single唤醒之后,处于就绪状态,会将等待队列中的线程放入到同步队列中。
为什么不建议用singleAll唤醒?

因为singleAll会将所有的线程都唤醒,然后全部加入到同步队列中,而同步队列中只有一个线程可以获取到锁,所以对于这种操作是无用功。

注意:AQS实质上拥有一个同步队列和多个等待队列。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值