打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
AQS --- 渐入佳境

上一讲了解了 AQS 是什么,接下来看看它到底是怎样的结构。

一. 工作原理

AQS 使用一个 volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取和排队工作,将每条要去抢占资源的线程封装成一个 node 节点来实现锁的分配,通过 CAS 来完成对 state 值的修改。

HashMap 进行 put 的时候,也不是直接存储 key value 键值对,而是将 key value 键值对封装成 Node 节点,然后用数组 + 链表 + 红黑树存储 Node。AQS 也类似,将要抢占资源的 Thread 封装成 Node节点。

二. 相关源码:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
   
    static final class Node {
       ……
       volatile int waitStatus;
       volatile Node prev;
       volatile Node next;
       volatile Thread thread;
       ……
    }

    private transient volatile Node head;
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;
}

看到这个是不是就清清楚楚明明白白真真切切了。首先 AQS 外层是 state + CLH 队列,state 表示同步的状态,默认是0,为0时表示可以获取锁,不为0时,线程就得老老实实到队列中排队去;CLH 队列就是一个有头结点和尾结点的双端队列,如下图:

           +------+  prev +-----+       +-----+
      head |      | <---- |     | <---- |     |  tail
           +------+       +-----+       +-----+

AQS 的内层是一个 Node内部类,这个 Node 类主要有两个指针 prev 和 next、一个 waitStatus 表示线程的状、,一个 Thread 类型的变量保存等待的线程。

三. 从 ReentrantLock 看 AQS:

之前说了 AQS 是 JUC 并发包的基石,那就从我们接触最多的 ReentrantLock 入手,揭开它的神秘面纱。

先来看看 ReentrantLock 的结构图:

结构图

首先它实现了 Lock 接口,其内部主要是一个 Sync 内部类,这个内部类又有两个子类,一个 FairSync 和一个 NonfairSync,分别用来实现公平锁和非公平锁。而这个 Sync 内部类,又是 AbstractQueuedSynchronizer 的子类。

1. 我们 new ReentrantLock 的时候做了什么事?

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
 public ReentrantLock() {
     sync = new NonfairSync();
 }

通过这个构造方法可以知道,实际上是构建了一个非公平锁。如果 new 的时候传了 true,调用的构造方法就是:

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code trueif this lock should use a fair ordering policy
 */
 public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
 }

所以传的是 true,构建的就是公平锁。

2. 公平和非公平有什么区别?

非公平锁源码:

final boolean nonfairTryAcquire(int acquires) {
 final Thread current = Thread.currentThread();
 int c = getState();
 if (c == 0) {
  if (compareAndSetState(0, acquires)) {
   setExclusiveOwnerThread(current);
   return true;
  }
 }
 else if (current == getExclusiveOwnerThread()) {
  int nextc = c + acquires;
  if (nextc < 0) // overflow
   throw new Error("Maximum lock count exceeded");
  setState(nextc);
  return true;
 }
 return false;
}

公平锁源码:

 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;
  }
 }
 else if (current == getExclusiveOwnerThread()) {
  int nextc = c + acquires;
  if (nextc < 0)
   throw new Error("Maximum lock count exceeded");
  setState(nextc);
  return true;
 }
 return false;
}

乍一看两段代码好像没啥不一样,其实不同之处在,if (c == 0)这段判断中。公平锁多了一个判断条件,即!hasQueuedPredecessors(),看看这个方法的源码:

public final boolean hasQueuedPredecessors() {
 // The correctness of this depends on head being initialized
 // before tail and on head.next being accurate if the current
 // thread is first in queue.
 Node t = tail; // Read fields in reverse initialization order
 Node h = head;
 Node s;
 return h != t &&
  ((s = h.next) == null || s.thread != Thread.currentThread());
}

这个方法也很简单,首先是头节点不等于尾节点,然后就是头节点的下一个节点为空或者头节点的下一个节点保存的 Thread 不等于当前的 Thread。简单地说就是看队列中有没有除了当前 Thread 以为的 Thread 在等待获取锁,有就返回 true,否则返回 false。所以公平锁就是多了这个判断,其他都一样。

下一篇文章将会从源码层面分析 ReentrantLock 的加锁过程,敬请期待!


扫描二维码

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
盘一盘 AQS和ReentrantLock
深入剖析AQS和CAS,看了都说好
图解AQS源码分析
【死磕Java并发】—–J.U.C之重入锁:ReentrantLock
Java并发-AQS及各种Lock锁的原理
我画了35张图就是为了让你深入 AQS
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服