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 true} if 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 的加锁过程,敬请期待!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容