1.5w字,30图带你彻底掌握 AQS!(建议收藏)( 四 )


几个类的关系如下:
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
我们先来剖析下非公平锁(NonfairSync)的实现方式 , 来看上述示例代码的第二步:加锁 , 由于默认的是非公平锁的加锁 , 所以我们来分析下非公平锁是如何加锁的
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
可以看到 lock 方法主要有两步

  1. 使用 CAS 来获取 state 资源 , 如果成功设置 1 , 代表 state 资源获取锁成功 , 此时记录下当前占用 state 的线程 setExclusiveOwnerThread(Thread.currentThread());
  2. 如果 CAS 设置 state 为 1 失败(代表获取锁失败) , 则执行 acquire(1) 方法 , 这个方法是 AQS 提供的方法 , 如下
public final void acquire(int arg) {if (!tryAcquire(arg) }tryAcquire 剖析首先 调用 tryAcquire 尝试着获取 state , 如果成功 , 则跳过后面的步骤 。 如果失败 , 则执行 acquireQueued 将线程加入 CLH 等待队列中 。
先来看下 tryAcquire 方法 , 这个方法是 AQS 提供的一个模板方法 , 最终由其 AQS 具体的实现类(Sync)实现 , 由于执行的是非公平锁逻辑 , 执行的代码如下:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 如果 c 等于0 , 表示此时资源是空闲的(即锁是释放的) , 再用 CAS 获取锁if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 此条件表示之前已有线程获得锁 , 且此线程再一次获得了锁 , 获取资源次数再加 1 , 这也映证了 ReentrantLock 为可重入锁int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}此段代码可知锁的获取主要分两种情况
  1. state 为 0 时 , 代表锁已经被释放 , 可以去获取 , 于是使用 CAS 去重新获取锁资源 , 如果获取成功 , 则代表竞争锁成功 , 使用 setExclusiveOwnerThread(current) 记录下此时占有锁的线程 , 看到这里的 CAS , 大家应该不难理解为啥当前实现是非公平锁了 , 因为队列中的线程与新线程都可以 CAS 获取锁啊 , 新来的线程不需要排队
  2. 如果 state 不为 0 , 代表之前已有线程占有了锁 , 如果此时的线程依然是之前占有锁的线程(current == getExclusiveOwnerThread() 为 true) , 代表此线程再一次占有了锁(可重入锁) , 此时更新 state , 记录下锁被占有的次数(锁的重入次数),这里的 setState 方法不需要使用 CAS 更新 , 因为此时的锁就是当前线程占有的 , 其他线程没有机会进入这段代码执行 。 所以此时更新 state 是线程安全的 。
假设当前 state = 0 , 即锁不被占用 , 现在有 T1, T2, T3 这三个线程要去竞争锁
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
假设现在 T1 获取锁成功 , 则两种情况分别为 1、 T1 首次获取锁成功
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
2、 T1 再次获取锁成功 , state 再加 1 , 表示锁被重入了两次 , 当前如果 T1一直申请占用锁成功 , state 会一直累加
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图


推荐阅读