• 场景化解释 AQS原理


    通过一个简单的例子描述场景:

    公平场景:

    现在同时有10个人需要在饮水机接水, 但是饮水机每次只能被同一个人使用, 那么最先到的当然可以直接接水,后来的则肯定需要等待, AQS使用的策略是, 在接水的地方放一块黑板, 每个来接水的人需要在黑板上写下自己名字, 若自己前面没人则直接写上名字,

    新来接水的人只要在后面接着写自己名字就行, 就出现  张三 - 李四 - 王二麻子。。。。, 张三排头, 王二麻子最后,  张三接完水后就呼叫李四来接水, 李四接完后就呼叫王二麻子来接水,以此类推, 本着先到先得后到后得的公平原则

    非公平场景:

    同上面一样, 并且也都是按规则在黑板上写下自己的名字, 但是唯一的区别是, 张三正好接完水准备叫李四的时候, 此时狗蛋刚好过来了,跟张三说了一句,你先别叫, 反正他们不知道,让我先接水,我接完了就帮你叫李四, 张三觉得反正自己也不亏什么就答应了,

    还有一种情况就是张三刚接完水,已经呼叫李四了,李四正在赶来路上,此时狗蛋也不敢乱来,于是就把自己的名字写在最后一个人的后面,跟其他人一样等着前一个人呼叫自己

    通过上面例子应该已经知道大致了解AQS的基本实现原理了(但远不止这些)

    先看数据结构

    public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
                            implements java.io.Serializable {
         //头节点
         private transient volatile Node head;
         //尾部节点
         private transient volatile Node tail;
         //状态 
         private volatile int state;
        //当前拥有锁的线程
        private transient Thread exclusiveOwnerThread;
        
        static final class Node {
    
            //节点等待状态 这个很重要 relase时会根据节点等待状态做出下一步动作
         //  1取消由于超时或打断而取消 不再阻塞
            // -1 等待被唤醒 后续节点需要阻塞 
            // -2 等待转移 非阻塞
            // -3 共享参数 需要继续传播给下一个节点   
            volatile int waitStatus;
            //前节点
            volatile Node prev;
            //后节点
            volatile Node next;
            //节点线程 这个是重点 保存了等待锁的线程
            volatile Thread thread;
            //下一个等待锁的节点 condition时有用
            Node nextWaiter;
        }
    
        public class ConditionObject implements Condition, java.io.Serializable {
            //condition 阻塞队列第一个线程节点
            private transient Node firstWaiter;
            //condition 阻塞队列最后一个线程节点
            private transient Node lastWaiter;
        }
    
    
        
    }

    可以把Node比作一个等水的人 到这里最关心的是下一个节点next 

    独占锁

    依然用场景描述代码:

    当第一个人A来装水时此时不需要排队,  Thread exclusiveOwnerThread 设置为当前装水的人,

    这个时候又有一个人B来装水, 通过对比当前线程与exclusiveOwnerThread,可知饮水机已经被占用了,那就乖乖排队,先找黑板上有没有人在等, 若没有B把自己名字写上去, 并且head 和 tail都是B, 然后B就静静的等待A的呼叫

    然后又有一个人C来装水,发现黑板上写着B的名字,此时C把名字写在B的后面,  也就是B.next=C C.prev=B,由于C排在最后面那么tail=C, 和B一样静静的等待B的胡唤就行了

    A装完了后找到head, 此时head==B, 那么就呼叫B, B装完水之后把自己名字从黑板上擦掉, head=C, 然后B就开始装水, 装完水后和当时A一样看到head=C,那么就呼叫C 以此类推

    队列图如下

    共享锁

    然后出现另一种情况, 这个饮水机除了可以装水外还能往里面加水,并且可以多人同时加水, 但是有人在接水的时候是需要等人接完才可以装水,可以在装水的同时接水

    然后就需要改变黑板上的内容,除了写名字还要写是接水还是倒水

    场景描述代码:

    继续上一个故事, 现在有D E F三个人需要给饮水机加水, H G 需要接水,   此时C正在接水, 那么DEFHG之前一样依次把自己名字写在黑板上, 但是加上自己是接水还是加水, 此时就是waitStatus=-3表示加水,waitStatus=-1为接水

    当C接完水在黑板上看到head=D,呼叫D过来, 因为装水的时候既可以装水,也可以接水, 那么D就直接呼叫E过来, 同理E和F都是加水, 最后F呼叫H来接水, 此时的场景是 DEF 在给饮水机加水, 同时H在饮水机接水,此时饮水机旁就存在 DEFH四个人

    那么这个时候饮水机就是被4个人共享的, 这就是共享锁

    误区

    可能有些人刚学多线程的时候搞不清重入非重入 与 共享锁非共享锁的区别, 是否重入与是否共享是两个维度的东西

    代码分析

    (代码略, 带着思路去看源码很容易就理解了)

  • 相关阅读:
    前端编程练习:电子表翻盘效果
    前端编程练习:钟表(下):使用js编写钟表
    前端编程练习:钟表(上):使用背景图实现效果
    使用原生js编写贪吃蛇小游戏
    前端编程练习:手风琴
    前端编程练习:电子表
    前端编程练习:放大镜
    Winfrom之domainUpDown控件
    Winfrom之MaskedText(高级文本框)
    Winfrom之ProgressBar控件(进度条)
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12162462.html
Copyright © 2020-2023  润新知