范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

并发编程AQS源码解析

  在并发编程中,ReentrantLock作为一个非常重要同步组件,基层是基于AQS同步器构建,我们一起以ReentrantLock源码,分析AQS工作原理。 简介
  AQS(AbstractQueuedSynchronizer)使用一个int成员变量state表示同步状态(state为0,表示当前资源没有被占用,state>0,表示当前资源已经被占用),结合内置的一个同步队列(FIFO)完成资源获取线程的等待排队工作,如果当前线程没有获取到锁,则将线程封装成一个Node节点,加入到同步队列,等待唤醒,通过自旋的方式尝试获得锁。
  Node节点组成 waitStatus:等待状态
  Node prev:前驱节点
  Node next:后继节点
  Thread thread:获取同步状态的线程
  Node nextWaiter:等待队列中的后继节点
  Node节点状态:waitStatus INITIAL=0:初始状态
  SIGNAL=-1:后继节点的线程处于等待状态,当前节点如果释放了同步状态,将通知后继节点
  CONDITION=-2:节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal方法后,该节点将从等待队列转移到同步队列中,加入到对同步状态的获取中
  PROPAGATE=-3:表示下一次共享式同步状态获取将会无条件的被传播下去
  CANNELED=1:在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消等待,节点进入该状态将不会变化源码分析
  1. 加锁
  首先从ReentrantLock的lock方法开始。 public void lock() {     sync.lock(); }
  可以发现ReentrantLock实际调用的sync对象的lock方法,sync对象是ReentrantLock内部的一个静态内部类。 abstract static class Sync extends AbstractQueuedSynchronizer {         // ... }
  sync类有两个是实现:FairSync、NonfairSync,分别对应公平锁和非公平锁。 先从非公平锁的角度分析源码,下文用sync代替非公平锁NonfairSync。  final void lock() {     if (compareAndSetState(0, 1))         setExclusiveOwnerThread(Thread.currentThread());     else         acquire(1); }
  sync类的lock方法先尝试通过CAS修改state的状态位1,如果修改成功,表示当前线程获取到锁。否则调用acquire方法尝试加锁。 public final void acquire(int arg) {     if (!tryAcquire(arg) &&         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         selfInterrupt(); }
  acquire方法内部再次尝试通过tryAcquire获取锁。 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; }
  从这个方法可以看出,如果当前线程已经拥有锁,则将状态+acquires(获取锁的次数,需要释放相同的次数,可重入)直接返回加锁成功。如果没有获取到锁,则返回false。
  回到acquire方法,假设tryAcquire没有加锁成功,继续跟踪addWaiter,通过方法名称可以猜出来,这个方法是将当前线程封装成一个Node。 private Node addWaiter(Node mode) {     Node node = new Node(Thread.currentThread(), mode);     // Try the fast path of enq; backup to full enq on failure     Node pred = tail;     if (pred != null) {         node.prev = pred;         if (compareAndSetTail(pred, node)) {             pred.next = node;             return node;         }     }     enq(node);     return node; }
  首先将当前线程封装成一个Node对象,如果当前队列是空的情况下,head和tail默认是都null,if (pred != null) 条件不成立,继续跟踪enq方法。  private Node enq(final Node node) {     for (;;) {         Node t = tail;         if (t == null) { // Must initialize             if (compareAndSetHead(new Node()))                 tail = head;         } else {             node.prev = t;             if (compareAndSetTail(t, node)) {                 t.next = node;                 return t;             }         }     } }
  第一次循环,t为null,if (t == null) 条件成立,通过CAS设置head节点为一个空的Node节点(哨兵节点),然后让tail和head同时指向哨兵节点。
  哨兵节点表示当前已经获得锁,正在执行业务逻辑的线程
  第二次循环,t不为null(t指向哨兵节点),node(当前线程封装的对象)的前驱指针指向t,即第一次循环创建的哨兵节点,通过CAS让tail指向node节点,然后将哨兵节点的后继指针指向node节点,结束循环。
  通过自旋的方式初始化了哨兵节点,并且将当前线程Node节点添加到同步队列中,假设此时有多个线程抢占锁,最后都会进入同步队列等待。
  继续跟踪acquireQueued方法,首先获取当前线程Node节点的前驱节点p,判断前驱节点p是否是头节点。通过上面的分析可知,p是头结点,然后通过通过tryAcquire方法尝试获取锁。 如果加锁成功,则将当前节点设置为头结点,并将之前创建的哨兵节点的next指针指向null,即断开哨兵节点与当前线程Node节点之间的关联,此时当前线程已经获取到锁。 如果加锁失败,则调用shouldParkAfterFailedAcquire方法。 final boolean acquireQueued(final Node node, int arg) {     boolean failed = true;     try {         boolean interrupted = false;         for (;;) {             final Node p = node.predecessor();             if (p == head && tryAcquire(arg)) {                 setHead(node);                 p.next = null; // help GC                 failed = false;                 return interrupted;             }             if (shouldParkAfterFailedAcquire(p, node) &&                 parkAndCheckInterrupt())                 interrupted = true;         }     } finally {         if (failed)             cancelAcquire(node);     } }
  头结点当前等待状态是初始状态,所以进入else代码块,设置头结点状态为SIGNAL(-1),继续进行下一次循环。 假设下一次循环依旧没有加锁成功,再次进入shouldParkAfterFailedAcquire方法,当前头结点状态已经变成SIGNAL,返回true,继续调用parkAndCheckInterrupt方法。 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {     int ws = pred.waitStatus;     if (ws == Node.SIGNAL)         return true;     if (ws > 0) {         do {             node.prev = pred = pred.prev;         } while (pred.waitStatus > 0);         pred.next = node;     } else {         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);     }     return false; }
  可以看出,当前线程被阻塞,进入等待状态。
  至此已经完成了线程加锁失败,进入同步队列排队等待源码分析,下面继续看等待线程如何被唤醒。 private final boolean parkAndCheckInterrupt() {         LockSupport.park(this);         return Thread.interrupted(); }
  2. 释放锁
  排队线程的唤醒必须要由其他线程释放锁触发,所以我们继续跟踪ReentrantLock的unlock方法。 public void unlock() {     sync.release(1); }public final boolean release(int arg) {     if (tryRelease(arg)) {         Node h = head;         if (h != null && h.waitStatus != 0)             unparkSuccessor(h);         return true;     }     return false; }
  ReentrantLock通过Sync的release方法释放锁,release调用了tryRelease方法,跟踪tryRelease方法源码。
  判断当前线程是否是锁的独占线程,如果不是,抛出异常。所以我们使用ReentrantLock的时候,必须要加锁成功,才能执行unlock方法。 如果state-releases的结果等于0,说明已经释放锁成功,清空当前锁的独占线程,重置状态为0。 这里必须要注意,我们加锁几次,就要释放几次。  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;         setExclusiveOwnerThread(null);     }     setState(c);     return free; }
  继续回到Sync.release方法,释放成功之后,如果头节点h不等于空,并且头结点h的等待状态不是0,调用unparkSuccessor方法(通过上面的分析可知,头结点的状态是-1,表示头结点的后继节点处于等待状态)。
  头结点h的状态等于SIGNAL(-1),CAS设置头结点状态为0,节点s(指向线程A对应的Node节点)不等于空,并且节点s的状态为初始状态(0),不会进入if条件,通过LockSupport.unpark唤醒线程A。 private void unparkSuccessor(Node node) {     int ws = node.waitStatus;     if (ws < 0)         compareAndSetWaitStatus(node, ws, 0);     Node s = node.next;     if (s == null || s.waitStatus > 0) {         s = null;         for (Node t = tail; t != null && t != node; t = t.prev)             if (t.waitStatus <= 0)                 s = t;     }     if (s != null)         LockSupport.unpark(s.thread); }
  如果线程A等待超时或者被中断的情况
  如果节点s的状态大于0(即线程A等待超时或者被中断),执行for循环,此时tail指向线程B对应的Node节点。 第一次循环,t=tail,t指向Node B,并判断Node B的等待状态,Node B是初始状态(0),t赋值给s,s指向Node B。 第二次循环,t=Node A(t = t.prev),Node A由于等待超时或者被中断,所以等待状态waitStatus等于1,if (t.waitStatus <= 0)不成立。 第三次循环,t=head(t = t.prev),for循环条件不成立,结束循环
  至此,s指向Node B,继续往下执行,通过LockSupport.unpark唤醒线程B。

英锦赛后最新斯诺克排名,谁的升幅最大?奥沙利文依然雄踞榜首随着2022英锦赛马克艾伦以107的比分击败丁俊晖夺得冠军,同时收获250000英镑冠军奖金,马克艾伦的排名上升4位,由第9位升至第5。获得亚军的丁俊晖也获得100000英镑进账,西部第一轰然倒下!伦纳德转型8分5助,快船轰3连胜杀入西部前四北京时间11月22日,NBA常规赛继续进行,快船坐镇主场迎战爵士。此前快船战绩为10胜7负,排名西部第八,而爵士战绩为12胜6负,排名西部第一。实话实说,西部前八名一共只相差1。5伦纳德被交易!快船队3换1追求巴特勒NBA新赛季比赛已经开始,作为每个赛季的夺冠热门球队之一的快船队再次让球迷们感觉做了一次过山车!伦纳德本赛季初伤愈复出,一度让球迷们惊呼,最强快船队已经正式开始向总冠军进军了!图片祛妍堂如何正确进行湿敷湿敷可以帮助减轻瘙痒干燥和炎症。如果皮肤是敏感或脆弱的,湿敷可能会起到改善作用。但有时候你不知道怎么用正确的方法进行湿敷。请继续阅读下文,了解如何正确进行湿敷吧!做好准备工作做好准25枚限量版腕表只为出席东盟会议及相关峰会的各国领导人特别定制柬埔寨作为第40届和第41届东盟会议及相关峰会的主席国和东道国,向出席峰会的各国领导人赠送了25枚限量版腕表。由柬埔寨太子钟表职业培训中心特别设计并打造的东盟莲花陀飞轮腕表彰显了东肯德尔詹纳已经采用了不穿裙子和裤子出去的奇怪时尚2022年的最新时尚潮流非常不寻常从名人到时装秀,造型都没有裙子和裤子。当我们走出家门时,它会发出各种各样的声音,它会吸引旁观者的注意,然而,不穿裙子和裤子出去的时尚行为将是一种解清洁必入,真珠美学泡泡洗面奶深层洁净还不刺激大油皮们每天都很苦恼,如果不想这样,清洁一定要做好,可以跟我一样用真珠美学泡泡洗面奶来洁面,温和不刺激还控油,真珠美学泡泡洗面奶堪称四季清洁必备。真珠美学泡泡洗面奶对于油皮来说,四2022年岁尾的疯言疯语年末岁尾,在一个地方两年,不全面,不客观,可还是想写点什么。来时谈不上意气风发,想走时也谈不上不舍与失望。虽然日子是一天一天过,可每到一个新地方,一年的开始,人总要憧憬些什么,这是你体验或想过死亡吗,被最爱的人背叛,打算向世界告别现在的我能感觉到身体的每一个变化,尤其是体温正在下降,想要动却如同被打了麻药一般无法行动,被湖底的黑暗一点一点的吞噬。这条河是在家附近不远处的一条河,几天前我还和自己的爱人一起在河快30岁的你今年怎么样?快30岁的年纪,有的人拥有了自己的家庭,有的人拥有了一个不错的事业,也有人拥有了不错的收入,但是反观自己,却好像什么都没有,唯一拥有的可能就是越来越大的年纪了。时间真的很快无论是成业内人士谈卡塔尔世界杯吉祥物商标被抢注成功率几乎为零极目新闻记者余渊见习记者舒隆焕卡塔尔世界杯已经火热开赛,吉祥物拉伊卜也已在开幕式上惊喜亮相。据了解,吉祥物拉伊卜的商标在国内已被抢注,目前正在注册申请中。业内人士告诉极目新闻记者,
闷声发大财,张绍刚,9年前被全网黑的债,还上了要说最近最火的综艺节目,不是鹿晗邓超陈赫的哈哈哈哈哈3,也不是即将收官的我们的客栈,更不是槽点满满的无限超越班,而是10个男孩不拿酬劳,单纯种地的种地吧。这个节目从策划开始就被全网它是天然杀菌菜,春季5元一斤!随手一炒,鲜嫩营养,特解馋俗话说春吃芽,夏吃叶,秋吃果,冬吃根。可以看出来每个季节的吃当季的时令食物才是养生之道,四季更替,适时而食,不时不食。今日为大家分享的这个食材是蒜苔,虽然一年四季都有,但大量上市的宝宝什么时候用枕头,抓住这三个信号宝宝刚出生后由于生理弯曲还没有发育,平躺时才能够顺畅呼吸,这个时候是不需要使用枕头的,如果宝宝在此时使用枕头,会制造一个压迫的弯曲气道,反而会造成呼吸不畅。所以,宝宝在颈前曲形成之蜂王浆作为顶级滋补品,坚持喝蜂王浆对身体有哪些好处?大家可还记得前几年的畅销品都有哪些吗?我们所热衷的商品从烧烤奶茶,到口罩,到退烧药,如今到了保健滋养补品,是不是一个很神奇的过程,其中也反映出了大家追求的变化,我们从追求享乐转变为建议春天吃3样,不但排毒,还促进代谢,身体舒适更畅快春天是一个万物复苏的季节,人体也和大自然一样,处于一个活跃的时期,进入了一个代谢旺盛的状态。此时肝脏器官非常活跃,趁着春季阳气升发,把身体毒素排出去,对于保持人体的精神状态和营养均妇女节给她准备几道菜,清爽好吃,营养高虾仁焗豆腐用料虾仁香菇豆腐马苏里拉奶酪盐适量宴友食用油葱适量姜适量料酒适量做法1虾去壳,加入葱姜水料酒盐,汆熟2加入香菇3加入豆腐块4将虾仁豆腐盛入盘中5撒上切碎的马苏里拉奶酪61这道菜了,营养搭配,鲜香开胃,做法简单居家过日子,一日三餐,吃得最多的还是一些家常菜。特别是一些荤素搭配的快手小炒,简单好做,做法又比较灵活,特别适合反复做。今天分享的这道黄瓜炒肉片,就是我家常做的一道菜,看似普通,但八道高钙家常菜,孩子长高的加速器,营养丰富,建议家长收藏大家好,这里是香姐说美食,每天分享好吃的家常菜。春天是孩子长高的黄金时期,孩子能不能长高个,建议抓住这个长高的季节,多给孩子补充足够的钙营养,长得就比同龄人要快。今天香姐整理了八道懒人必备的5道快手菜,好吃营养高,10分钟出锅,每天照做特省事导语懒人必备的5道快手菜,好吃营养高,10分钟出锅,每天照做特省事对于很多人来说,做饭是一件很困难的事情,但又想在家吃怎么办?不妨多学几道快手菜,不想费事的时候做着吃,省时省力不挨阅读活动引导幼儿积极处理负面情绪本报讯(通讯员林利)为了关心儿童心理健康,大渡口区图书馆九宫庙街道新工社区新时代文明实践站心理咨询公司联合三星幼儿园等单位,于日前开展了童心向党阅读悦美主题活动。活动在音乐游戏我的宝宝大便酸臭,4个原因妈妈要了解对于妈妈们来说,不管以前是否有洁癖,当妈之后,对宝宝的屎尿屁都会习以为常,甚至还会在宝宝排便排尿之后多看几眼,就是为了从宝宝的排泄物中得知他们的健康状况。不少妈妈在宝宝排便之后会发