通过一个简单的例子描述场景:
公平场景:
现在同时有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个人共享的, 这就是共享锁
误区
可能有些人刚学多线程的时候搞不清重入非重入 与 共享锁非共享锁的区别, 是否重入与是否共享是两个维度的东西
代码分析
(代码略, 带着思路去看源码很容易就理解了)