AQS原理剖析

并发编程是大厂面试中重点考察的问题。

此类问题回答的好坏会在很大程度上影响我们面试的得分。

 

1. AQS在java中的使用

给代码加锁,是java中处理并发问题的重要手段。

java中的很多锁都是基于抽象类AQS(AbstractQueuedSynchronizer)实现的。

如下表所示:

同步工具同步工具与AQS的关联
ReentrantLock使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。
Semaphore使用AQS同步状态来保存信号量的当前计数。 tryRelease增加计数,acquireShared减少计数。
CountDownLatch使用AQS同步状态来表示计数。 计数为0时,才可以执行acquire操作(CountDownLatch的await方法)。
ReentrantReadWriteLock使用AQS同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数。
ThreadPoolExecutorWorker利用AQS同步状态实现对互斥线程变量的设置(tryAcquire和tryRelease)。

 

2. AQS的实现机制

AQS提供了实现锁的机制,即CLH队列,同时通过ConditionObject实现了条件等待链表。

 

其中CLH队列如下:

 

  1. AQS使用 private volatile int state; 表示同步状态

  2. 通过内置的FIFO队列完成线程的排队

  3. 使用CAS对state进行修改。

 

3. AQS主要方法

AQS对外提供的用于实现锁的方法如下:

方法说明
tryAcquire(…)互斥模式获取锁
acquire(…)互斥模式获取锁,忽略中断
tryRelease(…)互斥模式释放锁
acquireInterruptibly(…)互斥模式中断则中止获取锁
tryAcquireShared(…)共享模式获取锁
tryAcquireNanos(…)互斥模式获取锁,忽略中断
tryReleaseShared(…)共享模式释放锁
release(…)互斥模式释放锁
acquireShared(…)共享模式获取锁
acquireSharedInterruptibly(…)共享模式中断即中止获取锁
tryAcquireSharedNanos(…)互斥模式获取锁,忽略中断
releaseShared(…)释放共享锁

 

基于AQS可以实现公平锁和非公平锁,互斥锁和共享锁。

 

4. ReentrantReadWriteLock

ReentrantReadWriteLock中的写锁实现了互斥锁,读锁实现了共享锁。

其中写锁的互斥性,用于保证不会并发执行写操作,保证了数据的一致性。

其中读锁的共享性,确保可以多线程同时读取,提升了读的优先级,对于读操作执行逻辑较重的场合,比较合适。

在抢锁的时候,只有一个写线程可以抢锁成功,但同时可能有多个读线程抢锁成功,由于读写互斥,容易引起读线程“饿死”的情况。

 

4.1. 互斥锁

写线程通过调用WriteLock的lock方法来获取互斥锁。

java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock#lock

 public void lock() {
     sync.acquire(1);
 }

 

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

该方法首先尝试获取锁,如果获取失败,就将当前线程封装为Node对象,追加到CLH队列中,休眠,等待抢锁。

 public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

之所以称为互斥锁,是因为该方法向CLH队列添加的节点是单个节点,只封装了一个线程。

关于互斥锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。

 

4.2. 共享锁

读操作通过调用读写锁ReadLock的lock来获取共享锁。

java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#lock

 public void lock() {
     sync.acquireShared(1);
 }

 

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared

该方法首先尝试获取共享锁,如果抢锁失败,则执行线程节点入CLH队列的操作。

 public final void acquireShared(int arg) {
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
 }

之所以称为共享锁,是因为在该方法中,会判断CLH队列尾节点是互斥操作还是共享操作,如果是互斥操作,则当前线程节点追加到尾节点后面;如果是共享操作,则通过引用,挂到尾节点的横向链表上,当获取锁的时候,横向链表中的节点线程一并获取到锁。

关于共享锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。

 

5. ReentrantLock

ReentrantLock实现了公平锁和非公平锁。

 

5.1. 抢锁操作

java.util.concurrent.locks.ReentrantLock#lock

该方法用于获取互斥锁。

 public void lock() {
     sync.acquire(1);
 }

 

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

上述方法调用了AQS的acquire方法,用于获取锁,该方法首先尝试获取锁,如果获取锁失败,就将当前线程封装为Node接待,追加到CLH队列中。

 public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

 

上述方法中的tryAcquire方法,该方法调用了ReentrantLock内部类Sync的实现。

Sync类有两个子类,分别是FairSync和NofairSync。其中FairSync实现了公平锁功能,NonfairSync实现了非公平锁的功能。

 

5.2. 公平锁

当前线程在抢锁的时候,先判断CLH队列中有没有其他线程在等待获取锁,如果没有,再去抢锁。如果有,则乖乖排队:

 protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
         // 如果没有前置节点等待抢锁,才去抢
         if (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     // ......

关于公平锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。

 

5.3. 非公平锁

 

tryAcquire方法的实现如下,该方法调用了nonfairTryAcquire方法用于实现非公平抢锁。

java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire

 protected final boolean tryAcquire(int acquires) {
     // 非公平尝试获取锁
     return nonfairTryAcquire(acquires);
 }

 

下面方法中,表示没有线程持有锁,不管是否有线程在等待,直接抢,非公平。

 final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
 ​
     if (c == 0) {
         if (compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     // ......

关于非公平锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值