JUC中的AQS底层详细超详解( 四 )

看一下ReteenLock中的tryRelease实现
就是减一下资源值 。
当资源值清零,则说明可以解除了对当前点的占用
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {free = true; // 设置当前占用线程为null setExclusiveOwnerThread(null); } // 不需要CAS , 因为只有持有锁的人才能做释放,不担心竞争 setState(c); return free; }AQS如何实现公平和非公平?以ReteenLock为例,它内部tryAcquire有两种同步器的实现

  • 非公平同步器NonfairSync
  • 公平同步器FairSync
公平同步器和非公平同步器都是ReentrantLock中定义的一个static内部类
ReentrantLock根据配置的不同,使用这2个同步器做资源的获取和同步操作
他们二者的提供的lock操作,本质上就是AQS的acquire(1)
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); }二者在公平和非公平的实现区别上,就是唤醒线程后,只有等待队列的队头节点才会尝试竞争 。
而非公平锁是只要唤醒了就可以尝试竞争 。
因此核心区别在于hasQueuedPredecessors方法!
JUC中的AQS底层详细超详解

文章插图
公平和非公平锁的优点和缺点
  • 饥饿问题
非公平锁可能引发“饥饿”,即一个线程反复抢占获取 , 而其他线程一直拿不到 。而公平锁不存在饥饿,只要排上队了就一定能拿到
  • 性能问题
非公平锁的平均性能比公平锁要高,因为非公平锁中所有人都可以CAS抢占 , 如果同步块的时间非常短,那么可能所有人都不需要阻塞,减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程 , 会减少唤起线程的数量 。
性能测试中公平锁的耗时是非公平锁的94.3倍, 总切换次数是133倍
Lock类是默认公平还是非公平?默认是非公平的,原因就是上文考虑的性能差距过大问题 ,  因此公平锁只能用于特定对性能要求不高且饥饿发生概率不大的场景中 。
独占模式和共享模式的AQS区别
  • 名字上 ,  共享模式都会带一个shard
  • 返回值上,独占模式相关acuire方法放回的是boolean类型,而共享模式返回的是int值
  • 核心概念上,区别在于同一时刻能否有多个线程可以获取到其同步状态
  • 释放时 , 共享模式需要用CAS进行释放, 而独占模式的release方法则不需要,直接setState即可 。
  • 共享模式应用:信号量、读写锁
共享模式信号量Semaphore的Sync同步器先实现了一个静态内部类Sync
和上面的RLock类一个区别在于需要state初始化值,不一定为1
Sync(int permits) { setState(permits); }再继承实现了FairSync和NoFairSync
使用CAS实现值的增加或者减少
公平/非公平的区别同样是hasQueuedPredecessors的判断
protected int tryAcquireShared(int acquires) { for (;;) { // 队头判断 , 公平锁核心 if (hasQueuedPredecessors()) return -1; int available = getState(); int remaining = available - acquires; // 信号量不足 , 直接返回负数 if (remaining < 0 || // 能抢成功,返回修改后的值,抢失败则for循环继续 compareAndSetState(available, remaining)) return remaining; } }AQS如何处理重入通过current == getExclusiveOwnerThread()来判断并进行非CAS的setState操作
if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; // 出现负数,说明溢出了 if (nextc < 0) // throw new Error("Maximum lock count exceeded"); // 因为是重入操作,可以直接进行state的增加,所以不需要CAS setState(nextc); return true;}注意处理重入问题时,如果是独占锁 , 是可以直接setState而不需要CAS的,因为不会竞争式地重入!
ReentrantLock释放时,也会处理重入 , 关键点就是对getState() - release后的处理,是否返回true或者false
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 只有资源数为0才会解锁 // 才算释放成功,否则这锁还是占住了free = true; setExclusiveOwnerThread(null); } setState(c); return free;}AQS如何响应超时AQS提供的方法中带有Nanos后缀的方法就是支持超时中断的方法 。
核心逻辑就是每次阻塞前 , 确认nanosTimeout是否已经超时了 。

推荐阅读