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

独占锁和共享锁底层原理分析

  独占锁模式
  前面说到,ReentrantLock锁就是基于独占锁实现的,独占锁的加锁和解锁操作都是通过互斥方式实现的。
  加锁流程
  在AQS中,是通过acquire()方法来加锁的,源码如下:    public final void acquire(int arg) {         if (!tryAcquire(arg) &&             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))             selfInterrupt();     } 复制代码
  首先映入眼帘的是tryAcquire(arg)方法,翻译过来是尝试获取锁的意思,由于是用&&连接,只有tryAcquire(arg)方法失败时才会往下执行,当tryAcquire(arg)方法成功返回true时表示获取资源成功,对于tryAcquire(arg)方法,里面没有实现具体的东西,只是抛出了一个异常,具体逻辑是由AQS的子类实现的:    protected boolean tryAcquire(int arg) {         throw new UnsupportedOperationException();     } 复制代码
  当tryAcquire(arg)返回为false时,会往后执行addWaiter(Node.EXCLUSIVE)方法,将当前线程封装成独占模式并添加到AQS的等待队列尾部,如果当tryAcquire(arg)返回true,则会直接让当前的线程继续执行,不需要添加到等待队列尾部,点入addWaiter(Node mode)方法,会看到如下源码:private Node addWaiter(Node mode) {                 /* 这个可以参考上面Node的构造方法         Node(Thread thread, Node mode) {     // Used by addWaiter             this.nextWaiter = mode;             this.thread = thread;         }         */                  //构造新的等待线程节点         Node node = new Node(Thread.currentThread(), mode);                 //新建临时节点pred指向尾节点         Node pred = tail;         //队列不为空的话,通过CAS机制将node放到队列尾部         if (pred != null) {             //将node的prev域指向尾节点             node.prev = pred;             //通过CAS机制将node放到队列尾部             if (compareAndSetTail(pred, node)) {                 //将原来尾节点的next域指向当前node节点,node现在为尾节点                 pred.next = node;//形成双向链表                 return node;             }         }         //如果队列为空的话         enq(node);         return node;     } 复制代码
  在多线程并发情况下,如果有多个线程同时争夺尾节点的位置,会调用enq(node)方法,使用CAS自旋机制挂到双向链表的尾部,下面是源码:    private Node enq(final Node node) {         //死循环(自旋)         for (;;) {             Node t = tail;             //尾节点为null,说明头结点也为null,可能是还没有创建队列的时候             if (t == null) {                  //多线程并发情况下,利用CAS机制创建头结点和尾节点,CAS保证此时只有一个头节点被创建,下次自旋时,就会满足队列不为空的条件                 if (compareAndSetHead(new Node()))                     tail = head;             } else {                 //如果存在尾节点,将当前节点的prev域指向尾节点                 node.prev = t;                 //利用CAS机制完成双向链表的绑定,让之前尾节点指向当前node节点                 if (compareAndSetTail(t, node)) {                     t.next = node;                     return t;                 }             }         }     } 复制代码
  接下来看一看compareAndSetTail方法使用CAS乐观锁机制的方法源码:    private final boolean compareAndSetTail(Node expect, Node update) {         return unsafe.compareAndSwapObject(this, tailOffset, expect, update);     } 复制代码
  上述代码讨论到,使用tryAcquire(int arg)方法返回false时,再使用addWaiter(Node mode)方法,将当前线程放入到队列的尾部。acquireQueued(final Node node, int arg)方法,等待休息直到其他线程唤醒    final boolean acquireQueued(final Node node, int arg) {         //拿到资源失败情况置为true,表示没有拿到资源         boolean failed = true;         try {             //用于判断线程是否中断过,默认没有中断过             boolean interrupted = false;             for (;;) {                 //获取node的前置节点                 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);         }     } 复制代码
  如果当前的前驱节点表示头结点,并且获取资源成功,那么直接将当前线程设为头结点,释放之前头结点与后继节点的链接,帮助垃圾回收(GC),如果前面当前节点的前驱不为头结点或者没有获取到资源,那么会调用shouldParkAfterFailedAcquire(Node pred, Node node)方法来判断当前线程是否能够进入waiting状态,如果可以进入,并且进入到了阻塞状态,那会阻塞,直到调用了LockSupport中的unpark()方法唤醒线程。    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {         //保存前驱节点的状态         /*         提示:         当waitState>0时,表示该线程处于取消状态(线程中断或者等待锁超时),需要移除线程;         当waitState=0时,默认值,表示初始化状态,表示线程还未完成初始化操作;         当waitState<0,表示有效状态,线程处于可唤醒状态。         */         int ws = pred.waitStatus;         //等待唤醒后置节点,SIGNAL为-1         if (ws == Node.SIGNAL)             return true;         //如果前置节点不是正常的等待状态(CANCELLED结束状态),那么从当前节点开始往前寻找正常的等待状态         if (ws > 0) {             do {                 //后面的节点断开与前驱节点的链接                 node.prev = pred = pred.prev;             } while (pred.waitStatus > 0);             //双向连接             pred.next = node;         } else { //小于0时,可能为共享锁             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);         }         return false;     }  复制代码
  如果前驱节点的SIGNAL值为-1,会返回true。
  compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法内部也使用了CAS锁机制,源码:    private static final boolean compareAndSetWaitStatus(Node node,                                                          int expect,                                                          int update) {         return unsafe.compareAndSwapInt(node, waitStatusOffset,                                         expect, update);     } 复制代码
  如果shouldParkAfterFailedAcquire(Node pred, Node node)方法返回true,则会调用parkAndCheckInterrupt()方法阻塞当前线程,线程等待,如果线程被中断过则返回true:private final boolean parkAndCheckInterrupt() {     // 调用park让线程进入wait状态     LockSupport.park(this);     // 检查线程是否中断过。     return Thread.interrupted(); } 复制代码
  如果线程在等待的过程中被中断过,那么获取到资源后会通知线程中断:    /**      * Convenience method to interrupt current thread.      */     static void selfInterrupt() {         Thread.currentThread().interrupt();     } 复制代码
  acquire()竞争获取锁资源流程时,首先会调用tryAcquire()方法去尝试获取资源,如果成功获取到资源,则直接进入临界区执行代码;如果没有获取到资源,则将此线程封装成一个结点放入队列尾部分,调用park()方法让线程等待,并且标记为独占模式。如果线程被唤醒(unPark)时,会尝试获取锁资源,如果在等待过程中,线程被中断过则返回true,没有被中断过返回false。如果线程在等待过程中被中断过,它是不会响应,只是在获取到资源后直接调用自我中断方法selfInterrupt(),将中断线程。
  释放独占锁
  在独占锁中,释放锁的入口是release()方法,源码如下:    public final boolean release(long arg) {         if (tryRelease(arg)) {             Node h = head;             if (h != null && h.waitStatus != 0)                 //唤醒后继节点                 unparkSuccessor(h);             return true;         }         return false;     } 复制代码
  点入tryRelease(arg)方法尝试释放锁,可以看出,其和tryAcquire()方法一样,也没有具体的实现,只是抛出了UnsupportedOperationException()异常,具体的逻辑由AQS的子类实现:    protected boolean tryRelease(long arg) {         throw new UnsupportedOperationException();     } 复制代码
  如果tryRelease(long arg)方法返回true,会判断头结点是否为空,并且waitStatus是否为0(为0则代码初始化阶段),如果不为0,那么下面会调用unparkSuccessor()方法,唤醒后继节点,我们来查看unparkSuccessor()方法的源码:    private void unparkSuccessor(Node node) {         int ws = node.waitStatus;         //如果当前状态为有效状态         if (ws < 0)             //CAS操作将waitState置为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);     } 复制代码
  unparkSuccessor(Node node)方法实现的是唤醒后继节点,当当前节点的waitStatus状态小于0时,表示该状态为有效状态,会使用CAS机制将当前线程设为初始化状态0,之后找到下一个需要唤醒的节点,如果下一个需要唤醒的节点为空或者为取消状态则将当前线程置为null,之后从尾节点往前遍历,寻找有效的节点,找到了且不为null的话,就唤醒该节点(线程)。共享模式加锁
  在共享模式下加锁的方法入口为acquireShared(long arg)方法,其源码如下:    public final void acquireShared(long arg) {         if (tryAcquireShared(arg) < 0)             doAcquireShared(arg);     } 复制代码
  进入到tryAcquireShared(arg)方法,此方法为尝试获取资源,得到如下源码,与独占模式获取锁一样,tryAcquireShared(long arg)没有实现具体的逻辑,由AQS的子类实现:    protected long tryAcquireShared(long arg) {         throw new UnsupportedOperationException();     } 复制代码
  tryAcquireShared(arg)会有三种返回值:当返回值为负数时,表示获取资源失败;当返回值为0时,表示获取资源成功,没有剩余资源;当返回值为正数时,表示当前线程获取到了资源,仍然有资源剩余。
  当tryAcquireShared(arg)方法返回为false时,表示获取资源失败,会往下进行doAcquireShared(arg)方法,此方法会将线程放入到等待队列尾部休息,点入 doAcquireShared(arg)方法的源码得:    private void doAcquireShared(long arg) {         //将节点加入到队列尾部         final Node node = addWaiter(Node.SHARED);         //获取资源成功的标志,初始为获取不到         boolean failed = true;         try {             //中断的标志             boolean interrupted = false;             //自旋             for (;;) {                 //获取当前节点的前驱节点                 final Node p = node.predecessor();                 //如果前驱节点是头节点的话                 if (p == head) {                     //返回还剩下多少资源                     long r = tryAcquireShared(arg);                     if (r >= 0) {                         //如果资源还足够的话,将头结点指向自己,如果还有剩余资源,可以唤醒后面的节点                         setHeadAndPropagate(node, r);                         //断开之前的头结点,便于垃圾回收                         p.next = null; // help GC                         if (interrupted)                             selfInterrupt();                         failed = false;                         return;                     }                 }                 if (shouldParkAfterFailedAcquire(p, node) &&                     parkAndCheckInterrupt())                     interrupted = true;             }         } finally {             if (failed)                 cancelAcquire(node);         }     } 复制代码
  在共享模式下,当线程被唤醒拿到资源时,如果还有剩余资源,会继续唤醒后继的线程。如果被唤醒的线程发现资源不够用时会再次进入休眠。这个情况下,就算排在首位线程后面的线程需要更少的资源,也会因为前面资源不够而等待,不会先执行后面的线程。
  对于上面代码中出现的setHeadAndPropagate()方法,点入查看得到以下源码:    private void setHeadAndPropagate(Node node, long propagate) {         //将头结点赋值为h         Node h = head; // Record old head for check below         //将当前节点设置为头结点         setHead(node);         //如果还有剩余资源的话,会唤醒后面的节点(线程)         if (propagate > 0 || h == null || h.waitStatus < 0 ||             (h = head) == null || h.waitStatus < 0) {             Node s = node.next;             if (s == null || s.isShared())                 //唤醒后继节点                 doReleaseShared();         }     } 复制代码
  释放共享资源
  释放共享资源方法为doReleaseShared()   private void doReleaseShared() {         //自旋         for (;;) {             Node h = head;             //头结点不为空并且头结点不等于尾节点             if (h != null && h != tail) {                 //拿到头结点的等待状态                 int ws = h.waitStatus;                 //如果当前节点为等待唤醒的节点                 if (ws == Node.SIGNAL) {                     //将当前节点等待状态初始化,来唤醒线程                     if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))                         continue;                                 // loop to recheck cases                     unparkSuccessor(h);//唤醒后继线程                 }                 //如果线程处于初始化状态,并且还有剩余资源                 else if (ws == 0 &&                          !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))                     continue;                // loop on failed CAS             }             //如果没有后继节点,退出自旋             if (h == head)                   // loop if head changed                 break;         }     } 复制代码
  在 doReleaseShared()方法中,通过自旋的方式获取头节点,当头节点不为空,且队列不为空时,判断头节点的waitStatus状态的值是否为SIGNAL(-1)。当满足条件时,会通过CAS将头节点的waitStatus状态值设置为0,如果CAS操作设置失败,则继续自旋。如果CAS操作设置成功,则唤醒队列中的后继节点。
  如果头节点的waitStatus状态值为0,并且在通过CAS操作将头节点的waitStatus状态设置为PROPAGATE(-3)时失败,则继续自旋逻辑。
  如果在自旋的过程中发现没有后继节点了,则退出自旋逻辑。
  本篇文章就分享到这里了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 ~
  文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论 希望能和诸佬们一起努力,今后进入到心仪的公司 再次感谢各位小伙伴儿们的支持
  作者:小威要向诸佬学习呀
  链接:https://juejin.cn/post/7164692725332181022

新春走基层看发展梭嫂的新春贺礼东营日报社爱东营讯春节期间,在利津县明集乡东望参村,古窑十八梭非遗工坊的传承人刘利红带头设计制作了别具风情的老粗布手工礼,献礼兔年新春。刘利红边向记者展示新作品,边说这是我设计的黄行走自贡富顺西部的板桥老街提起自宜边界重镇板桥,相信许多人都不会陌生,几年前由于时间的关系只是匆匆路过一次。去年底,行走乡间发现自贡从沿滩九洪场出发,途经三河场张坝白房,最后在板桥镇收官,第一次较为完整地探嘉定这些古镇老街,轨交11号线均可到达丨爱申活暖心春大年初三啦假期大家是怎么过的?坐着玩手机躺着玩手机站着玩手机是不是宅不住啦?来!小嘉给你推荐几处古镇老街,轨交11号线均可到达哦南翔古镇南翔古镇可谓大名鼎鼎它是上海四大历史文化名镇初九是天日,老话天日吃3样,不富也安康,吃3样指啥天南地北大拜年正月初九俗称天公生,中国传统农历节日之一。传说中是玉皇大帝的生辰。也叫天日又叫玉皇诞,按照老百姓的话说,也就是老天爷的生日,玉皇大帝在中国的神话中主宰三界,天上地下各红蜻蜓2022年预计亏损2500万元3750万元中证智能财讯红蜻蜓(603116)1月29日晚间披露2022年度业绩预告,预计2022年归母净利润亏损2500万元3750万元,上年同期盈利2253万元扣非净利润亏损6800万元8陈嘉庚的责任观华侨旗帜民族光辉来自前线客户端陈嘉庚纪念馆近日,又一次走进陈嘉庚纪念馆。馆内第一展厅入口处,墙上一块长方形牌匾,习近平总书记2014年10月17日写给厦门市集美校友总会的回信,红底金字,引人注目。路边小卖店隐藏的3款廉价酒,别看名不见经传,却是王者实力人无论处于社会的哪一个高度,他们最向往的仍是最初生活的美好。就拿消费终端的演变来说,各种大型商超一应俱全,但我们身处其中往往会产生选择困难症,若是到了路边小卖店中,其门店虽小但五脏编辑说湖南电子音像出版社20位优秀共产党人坚守初心使命的感人故事忠诚视频加载中忠诚选取中国共产党各个历史时期中具有典型意义的20位优秀共产党人,包括中国工人运动的先驱者林祥谦断肠铭志的湘籍烈士陈树湘深藏功名的时代楷模张富清用知识改变贫困女孩命运的好艾布拉姆斯的作为有多大,取决于乌军怎么用!美国将向乌克兰运送31辆M1艾布拉姆斯主战坦克,来帮助乌克兰抵御俄军。美国没有给出艾布拉姆斯的交付时间表的原因是美国还没有决定未来的路线,这涉及一个长长的选项清单,除了坦克之外,还春运期间增加线路延长运营时间,广州力保市民出行平安顺畅今天是春运的第23天,广州市也召开了2023年春节复工后第一场新闻发布会。40天的春运已经过半,据交通运输各方面相关负责人介绍,截至昨日,广州到发旅客一千六百万人次,白云机场近期国任振鹤更加自觉坚持和捍卫两个确立用实干实绩书写富民兴陇新答卷原标题任振鹤在看望慰问省政府办公厅机关干部时强调更加自觉坚持和捍卫两个确立用实干实绩书写富民兴陇新答卷春节假期后上班第一天,省委副书记省长任振鹤看望慰问省政府办公厅机关干部,代表省
三九班2023年新春致辞2023年新春致辞三九班的兄弟姐妹们50多年前,我们肩负着父母的希望,怀揣着不同的美好理想,从不同的地方相聚到三中。三年的校园生活,同一个教室相识相知,在我们的人生阅历中留下珍贵的我们的节日春节快来解锁都江堰新春精彩活动吧HAPPYNEWYEAR2023金兔祈祥迎新春守福待兔2023年春节已进入倒计时如何过一个热热闹闹好耍又特别的快乐年呢快把目光聚焦到仙气儿飘飘的青城山都江堰吧!潮玩元宇宙灌州集市沉宝鸡植物园内悬挂4000余只大红灯笼欢欢喜喜迎新春(宝鸡新闻网罗君)大红灯笼高高挂,欢欢喜喜迎新春。1月18日,记者在宝鸡植物园内看到,4000余只大红灯笼悬挂在园区主干道,一派喜庆祥和的春节氛围。为了突出兔年元素,植物园在东门南宝妈用这一招,让熊孩子秒变小暖男这次媳妇私自带着儿子去旅游,让熊气十足的儿子变成了妈妈的小暖男。网图上周收到媳妇发来的一条微信,内容是一张火车票的订单截图。这是一段从武汉到景德镇的往返行程。但人数只有两人,她这是宝藏之城美在潮州!潮州致力打造中小城市美的典范登上凤凰山顶,露营打卡,赏月观星穿过广济桥,微风拂面,韩江景色尽收眼底漫步古城街巷,品味美食茶香,畅享潮式慢生活千年文化秀丽山水古城风韵品质生活,均是潮州美的力量和底气。潮州着力打新春走基层金牌工匠许爱霞精雕细琢用真心还顾客清晰世界时光悄无声息,但总会留下印记。对于许爱霞而言,在姜玉坤总店橱窗里陈列的山东省技能兴鲁职业技能大赛全国第五届眼光与配镜山东省选拔赛中荣获验光职业组一等奖淄博金牌工匠张店区金牌工匠等证工匠多从揭榜来原标题面向一线职工张榜纳贤,激发蕴藏在基层的创新火花(引题)工匠多从揭榜来(主题)工人日报中工网记者陈华通讯员施院生阅读提示为有效破解影响企业发展的制约性难题,充分发挥职工群众的聪反复欣赏文娱作品,63。2受访者是出于怀旧情怀中青报中青网记者王品芝实习生肖平华近年来,网络小说电影连续剧综艺短视频中的经典作品,被青年反复回看,打开一些热门视频,再来亿遍每日一刷等弹幕也屡见不鲜。近日,中国青年报社社会调查中明日大寒,有条件记得吃3肉食3蔬忌3味,做好进补收尾期冬日生活打卡季大寒是年前的最后一个节气了。所谓寒气之逆极,故谓大寒,意思就是说大寒时节气温是最低的,极其寒冷,也是进入四九最冷的一天。过了大寒下一个节气就是立春了,我们离春天已经不惠阳奖补1000万元促经济,鼓励工业企业满产能商贸企业增营收临近春节,1月19日,惠阳提前出招,出台惠阳区促进2023年第一季度经济稳增长行动方案(下称方案),拿出1000万元奖补,真金白银鼓励工业企业满产能商贸企业增营收,以增强企业信心,每家企业都在苦苦挣扎!丰田章男对电动车说出真心话,丰田汽车却经历大冒险?每经记者董天意每经编辑孙磊据华尔街日报报道,丰田汽车社长丰田章男日前表示,丰田汽车正在考虑推出一个可以制造各种电动汽车的通用型平台,与当下生产电动汽车的平台完全不同。电动汽车需要独