AQS是什么:
1、 他是什么 :是ReentrantLock(独占)、Semaphore(共享)、CountDownLatch(共享)的核心基类。AQS有共享,独占两种模式。通过继承实现他的模版方法tryAcquire()、tryRelease()使用。
2、 用什么场景 :独占(串行执行的代码块,需要保证原子执行),就可以用到它再临界区加锁解锁
3、 独占实现:
用state = 0 表示没线程竞争到锁资源。大于1表示锁被重入。
多个线程去CAS去竞争state变量,没有竞争到锁的线程会包装成Node去FIFO队列排队阻塞。
释放锁的线程去唤醒队头的线程争抢锁。
NonfairSync非公平的实现是不看队列有没有元素直接去CAS尝试争抢。
FairSync公平的实现是去队列排队。
AQS小细节:
为什么用CAS:防止多线程赋值state出错。
CAS是返回bool类型,JAVA虚拟机的CAS实现保证了原子性( 根据一个汇编的宏操作对多cpu前的cmpxchg指令加lock。因为cmpxchg本身不保证原子性( CAS本身要保证原子性,至少if判断和写回中间不能被打断)
注意:汇编本身指令也不保证原子性,一条指令可能会分成好步骤,中间被打断,一句.java编译成好几句.class就是JVM的汇编,JVM会把好几句汇编翻译成本地的汇编会变成更多句。
state状态,子类通过继承并通过实现它的方法管理其状态(acquire获取许可和release释放许可)。
Thread引用指向的当前已经获取到锁的线程;
Node节点成员
1、 共享还是独占模式下的Node等待标记。
//该节点是,在共享模式下等待的标记
static final Node SHARED = new Node();
//节点在是,在独占模式下等待的标记
static final Node EXCLUSIVE = null;
2、 节点状态
//独占锁需要的两个常量
//waitStatus值表示线程已经取消
static final int CANCELLED = 1;
//waitStatus值表示后继线程需要解除阻塞
static final int SIGNAL = -1;
//共享锁需要的两个常量
//waitStatus值表示线程正在等待
static final int CONDITION = -2;
//waitStatus值表示下一个被请求的应该无条件地传播
static final int PROPAGATE = -3;
//节点状态。
volatile int waitStatus;
//状态字段,只接受值1、-1、-2、-3、0五种值
//SIGNAL信号:此节点的后继马上被阻塞(通过park阻塞) 如果当前节点释放了同步状态或被取消,会通知后继节点去获取同步锁。
//CANCELLED取消:该节点因超时或中断被设置为取消值。这个节点不参与锁竞争且状态不会再改变。
//CONDITION条件:表示线程在等待触发条件(condition)当其他线程调用了Condition的signal()方法后,CONDITION状态的节点将从等待队列转移到同步队列中,等待获取同步锁,此节点当前处于条件队列中。它不会被用作同步队列节点直到转移,此时状态将设置为0。
(此处使用此值有和这个词的其他用法没有关系Field,但简化机制。)
//PROPAGATE传播:表示下一个acquireShared应无条件传播的waitStatus值。前驱节点不仅会唤醒其后继节点,同时也可能唤醒后继的后继节点。
//前驱
volatile Node prev;
//后继
volatile Node next;
//包装的线程
volatile Thread thread;
//下一个等待条件出发的节点
Node nextWaiter;
加锁过程:
尝试获取锁的线程失败会,包装成Node加入到等待队列尾部,插入到尾部的过程会一直CAS直到插入成功
拿到当前节点的pre节点,
如果pre是头节点,并且尝试获取锁成功了,把头节点删除,设置当前节点为头节点,
如果pre节点不是头节点,也没有尝试获取到锁,就判断当前线程是否需要阻塞,
解锁过程:
获取当前节点状态(waitState值)如果小于0,把waitState设置为0,因为这个节点已经释放锁了不需要等待状态了,
读取当前节点的后继节点,如果后继节点waitState > 0 被取消或者后继节点为空,就从尾节点找到第一个有效节点唤醒。
为什么队列要用双向链表?
双链表有可以找前驱的指针。公平模式时:队列里抢占锁的节点会找到前驱是不是头节点。pre是头节点之后,再把头节点从队列里删除(头节点已经拿到了锁),把当前节点设置为头节点。
前驱不是头节点,说明我们可以阻塞,避免占用UPU资源。