多线程学习笔记之 - AQS

1.什么是AQS?

AQS,指的是一个抽象类,全名为    java.util.concurrent.locks.AbstractQueuedSynchronizer.它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。

AQS的基本数据结构:

它维护了一个state字段,表示当前资源的状态(0表示未被持有,1表示被线程持有,1以上表示重入的次数)

和一个FIFO的队列


AQS大致数据结构

以ReentrantLock为例子,对AQS的源码进行分析

2.ReentrantLock

ReentrantLock是JUC包下的一个同步工具类,基于AQS实现。


public class ReentrantLock implements Lock, java.io.Serializable {

    private static final long serialVersionUID = 7373984872572414699L;

    /** Synchronizer providing all implementation mechanics */

    private final Sync sync;

     ...

可以看到,它实现了lock接口,并且包含一个Sync的实例对象.该对象继承了AbstractQueuedSynchronizer类,并提供了两种实现

1.NonfairSync   非公平锁

2.FairSync         公平锁

以公平锁为例,阅读ReentrantLock 源码

首先从构造方法开始

/**

* Creates an instance of {@code ReentrantLock}.

* This is equivalent to using {@code ReentrantLock(false)}.

*/

public ReentrantLock() {

        //无参构造方法,默认把sync初始化为非公平锁

        sync =new NonfairSync();

}

/**

* 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) {

            //含参构造方法,true的时候将sync初始化为公平锁,否则是非公平锁

            sync = fair ?new FairSync() :new NonfairSync();

}

lock方法(翻译了源码的注释):

<p>如果锁不是由另一个线程持有,则获取该锁并返回*立即,将锁定保持计数设置为1。 

<p>如果当前线程已经持有锁,则*count递增1,方法立即返回。

<p>如果锁由另一个线程持有,则*当前线程在线程调度中被禁用*目的和休眠直到锁被获得,此时锁定保持计数设置为1。

public void lock() {

    sync.lock();

}

可以看到,调用了sync的lock方法。因为Sync是一个抽象类,要看具体实现.在IDEA中,Crtl+alt+B 可以跟踪实现类,进入FairSync查看该方法。

下面是该类的实现:

公平锁FairSync的实现

可以看到,sync的lock方法调用了acquire(1)这个方法。该方法是AQS类的方法:

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

}

tryAcquire(arg) 是尝试加锁的方法,尝试成功,返回true

当尝试加锁失败时,就需要调用acquireQueued()方法,把自己加入线程队列中,然后Park()


下面分析公平锁FairSync的tryAcquire()方法:


公平锁FairSync的tryAcquire()方法

1.首先获取当前线程、

2.获取lock对象的上锁状态,如果锁是自由状态则=0,如果被上锁则为1,大于1表示重入

3.当锁是自由状态时,调用hasQueuedPredecessors方法,判断自己是否需要排队。(因为是公平锁,要保证来的早的线程可以先拿到锁,所以在进来之前,要先判断一下是否需要排队。非公平锁会在这里会直接上锁,不平判断是否需要排队。)

        如果不需要排队,则尝试CAS加锁。加锁成功,设置当前线程为排它的拥有者,返回true,lock方法执行完成。

4.如果C不等于0,而且当前线程不等于拥有锁的线程则不会进else if 直接返回false,加锁失败

5.如果C不等于0,但是当前线程等于拥有锁的线程则表示这是一次重入,那么直接把状态+1表示重入次数+1

当这个方法尝试加锁失败,会返回acquire,这里有取反,所以条件成立,执行&&后面的逻辑

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

}

此时程序来到了addWaiter(Node.EXCLUSIVE)方法。

Node.EXCLUSIVE = null;是AQS类的一个工具变量


addWaiter方法

这里的Node是AQS队列的链表节点,因为是双向链表,所以要有前后指针。值放的就是Thread对象。

addWaiter方法的作用,就是往链表添加一个等待节点。做的操作如下:

1.当前线程装入节点

2.队尾指向一个临时变量

3.判断队尾是否为空

    判断pred是否为空,其实就是判断对尾是否有节点,其实只要队列被初始化了对尾肯定不为空。假设队列里面只有一个元素,那么对尾和对首都是这个元素,其实这里就是判断队列有没有初始化.

        不为空时,代表队列已经初始化。先将当前节点T1的前驱指向队尾,然后通过CAS操作(竞争激烈时确保原子操作),把队尾的后继替换成自己,返回出去。


当前线程入队

        队尾为空时,意味着队列没有初始化。调用enq方法,进行初始化。

enq方法:


enq方法

第一次循环,队尾是null

那么,使用CAS,把链表的头部设置成一个空节点,并且让队尾等于队首。此时,队列里就初始化出来了一个空节点。

进入第二次循环

    队尾现在是一个空对象,但不是null。将当前的节点的前驱设为队尾,然后CAS把队尾替换成自己,返回。


初始化队列

然后就进入了

        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))


acquireQueued

第一次循环,获取当前线程所在节点的上一个节点.

1.如果上一个节点是head,表示自己是第一个排队的,那么此时,有可能资源已经被释放了,所以再次尝试加锁。

2.尝试成功的话,表示该线程得到锁,然后把自己修改成头结点(setHead方法里的操作,是把头结点设置成自己,然后把该节点的前驱设为空,内容设为空.由此可见,队列中的头结点,永远是空节点)。

3.如果不是第二个节点,或竞争失败,那就应该去排队了。shouldParkAfterFailedAcquire()方法里,是修改waitStatus。他会获取上一个节点的waitStatus,如果是-1,表示上一个节点已经release,无需再操作,直接返回。如果大于0,表示上一个节点已经取消(?这里涉及到unlock,以后再说).如果是==0,就通过CAS操作把上一个节点的waitStatus改成-1,表示上一个节点以已经PARK了。

所以,正常情况下队列中的节点,应该是这样的:

waitstatus状态

因为上一个节点park后,已经不能继续执行了,所以,是由下一个节点来修改它的状态的。

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