AQS同步原理(更新)

AQS维护了俩个节点的引用,一个指向头节点,一个指向尾节点。当有一个线程成功获取到同步状态(或者锁)时,其他的线程就无法获取到同步状态,因此会被构造成节点放入同步队列,但是入队的过程必须保证是线程安全的。因此同步器提供了一个基于cas的设置尾节点的方法:compareAndSetTail(Node expect, Node update),设置成功后,当前节点正式与之前的尾节点建立关联。

同步队列遵循FIFO原则,首节点是获取同步状态的节点,当首节点的线程释放同步状态时,将会唤醒后继节点,而后继节点在获取同步状态成功的时候将自己设置为头节点。

设置首节点是通过成功获取到同步状态的线程来完成的,因为同时只有一个线程能够获取到同步状态,因此设置头节点并不需要用CAS来保证。它只需要将首节点设置为原首节点的后继节点,并且断开原首节点的next引用。

独占式同步状态的获取:

首先当调用lock方法时,会调用acquire(int arg),acquire方法首先会执行一段判断:if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))。判断结果如果返回true,则当前线程执行中断selfInterrupt();。(这里需要注意,如果当前的线程获取同步状态失败后进入了同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出,这里中断的意义何在???)

首先分析tryAcquire(1),这个方法会尝试更新当前的线程获取同步状态,如果当前预期同步状态为0,并且成功更新了当前线程的同步状态为1,并且将当前线程设为独占线程,返回true;如果更新不成功,则返回false。

再分析acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法。首先addWaiter方法,首先会以当前线程创建一个node节点a,在获取当前同步队列的tail节点,如果tail节点不为空,则利用cas更新当前a为tail节点,更新成功则返回a节点,更新失败则执行enq(node)方法(node传节点a);如果tail节点为空,也执行enq方法。

private Node enq(finalNode node) {

for(;;) {

Node t =tail;

if(t ==null) {// Must initialize

if(compareAndSetHead(newNode()))

tail=head;

}else{

node.prev= t;//将tail节点指向node的pre节点

if(compareAndSetTail(t,node)) {//如果t节点是尾节点,则将node节点更新为尾节点,cas是为了保证在更新是t是尾节点,因为在这个方法的执行过程中,可能有其他线程被更新为尾节点。如果更新失败,则继续更新,将tail置为t,循环前面的过程。

t.next= node;//将node置为t的next节点,此时node是尾节点。(即将t.next节点指向了node所指向的内存。node是当前线程构造的节点)

returnt;

}

}

}

}

分析enq方法,进入循环:第一次循环时,获取tail节点(t = tail;)。1、如果t节点为空,则初始化一个head空节点,让t = head,接着进入第二次循环;2、如果tail不等于空,则利用cas将a节点置为tail节点,如果失败,则接着执行循环,直到将a节点成功置为tail节点后,并且将a节点设置为t.next,返回t节点(1、addWaiter(Node.EXCLUSIVE)执行成功后返回的始终是以当前线程构造的node节点,而在构造过程中,当执行到enq(node)方法,只是为了保证将构造的node节点确保能插入到tail节点。2、enq(node)方法这里为什么不返回a节点,而是返回t节点,其他地方可能会用到,在此不做分析)。。(分界线)。。如果第一次循环时,t为空,则进入第二次循环,这个时候,t在第一次循环时已经被设置为空节点,并且是头节点,接着将a节点置为tail节点,利用循环cas直到成功的将a节点置为tail节点,并且将a节点设置为t.next,然后返回t节点。执行完addWaiter(Node.EXCLUSIVE)后,

程序进入到acquireQueued(node, arg),直接进入for循环开始分析。第一次循环时,理想状态下,首先获取node节点的pre节点p(如果node节点没有pre节点,直接抛空指针,因为之前的addWaiter的循环cas保证了node节点一定是tail节点,并且有pre节点),

如果p节点是头节点并且node节点的tryAcquire(1)方法返回true,代表node节点获取到了同步状态,那么将node节点设置为头节点,将p节点的next设为null(帮助gc),失败状态(failed)为false,中断状态返回false(保证了当前线程不会执行selfInterrupt方法)。如果p节点不是头节点或者node获取同步状态返回false,则接着往下执,if(shouldParkAfterFailedAcquire(p,node) &&parkAndCheckInterrupt())。

首先分析shouldParkAfterFailedAcquire方法,如果p节点的状态是-1,则直接返回true(This node has already set status asking a release to signal it, so it can safely park.)。如果不为-1,往下执行,1、如果状态大于0,则将p节点的pre节点置为p节点,并且将p节点置为node的pre节点,直到p节点的status不符合大于0的条件,终止while循环,然后将node节点置为p节点的next节点,返回false。2、如果状态小于0,则将p节点的状态设置为SIGNAL(-1)。返回false。这里返回false表示虽然acquire获取失败也不需要中断线程。返回true代表要中断线程。注意:这里shouldParkAfterFailedAcquire如果返回了false,代表当前的p已经变成了p节点的pre节点(这个pre节点也有可能是pre的pre节点,直到满足while结束。)在acquire第二次循环的时候,这个时候shouldParkAfterFailedAcquire可能会返回false,或true,因此如果第一次返回的是false,那么p的状态已经是一个小于0的数,当是-1,第二次直接返回true,如果不是-1,第三次循环返回true,因为在第二次的时候如果不是-1,则会置为-1。(这个方法的最终目的是使node的pre节点的状态为signal,这样node节点就有机会获取同步状态了)

但是acquireQueued是一个循环cas方法,直到p节点是头节点并且当前node获取到同步状态才会返回,如果在循环过程中出现异常,则会直接进入finally方法,就算正常结束,也会进入finally,判断failed后,如果为true,执行cancelAcquire(node);方法取消获取同步状态。将线程的状态置为1。

当有节点释放同步状态后,将会唤醒其后继节点线程,使用unparkSuccessor(Node node)来唤醒等待状态的线程。然后被唤醒的线程会获取同步状态,并将自己置为head节点。

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

推荐阅读更多精彩内容