场景化解释 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个人共享的, 这就是共享锁

误区

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

代码分析

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

原文地址:https://www.cnblogs.com/xieyanke/p/12162462.html