赞
踩
AQS即AbstractQueuedSynchronizer ,提供加锁解锁的一套模板,具体实现细节由子类实现,可以通过"三板斧"的辅助概念来理解:
private volatile int state;//同步状态变量
state为整个AQS的核心,是全局共享的一个状态,为保证其修改的可见性,用volatile修饰。state在不同的子类中有不同的含义。
以ReentrantLock为例,state表示该锁被线程重入的次数:当state=0时表示该锁不被任何线程持有;state=1表示当前线程持有该锁1次;state>1表示当前线程重入锁的次数;
state有三种访问方式:getState();setState();compareAndSetState(),都是原子操作。
同步等待队列是一个Node节点的双向链表来实现,队列采用悲观锁的思想,即当前线程需要获取锁时就有其他线程也来获锁。因此,它会把当前线程包装成一个Node节点,放入等待队列中,当一定条件满足后,再从等待队列中移除。
Node节点
- volatile int waitStatus;//当前节点等待的状态
-
- volatile Node prev;//前驱
-
- volatile Node next;//后继
-
- volatile Thread thread;//当前节点的线程
waitStatus的状态有5种:
- static final int CANCELLED(1);//当前节点已取消调度,当timeout或者中断会触发更新成改状态,终态
-
- static final int SINGAL(-1);//后继节点在等待当前节点唤醒,当后继结点加入时会触发更新为改状态
-
- static final int CONDITION(-2);//结点等待在Condition上,当其他线程调用了Condition的signal()方
- //法后,CONDITION 状态的节点将从等待队列转移到同步队列中,等待获取同步锁
-
- static final int PROPAGATE(-3);//共享模式下,前驱结点不仅会唤醒后继结点,也有可能唤醒后继的后继
-
- 0;默认状态
- private transient volatile Node head;
-
- private transient volatile Node tail;
head结点:是一个哑结点(dummy node),它不代表任何线程,一次head所指向的Node的thread永远是null。只有从次结点往后的所有结点才表示所有等待锁的线程。
AQS定义了两种资源共享方式:独占式(Exclusive)和共享式(Share)。
AQS只是一个框架,只定义了一个接口,具体资源的获取、释放都交由自定义同步器去实现。不同的自定义同步器争用共享资源的方式也不同,自定义同步器在实现时只需实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护,如获取资源失败入队、唤醒出队等,AQS已经在顶层实现好,不需要具体的同步器再做处理。
ReentrantLock对AQS的独占方式实现为:
CountDownLatch对AQS的共享方式实现为:
这个要看加入的地方
- private Node enq(final Node node) {
- for (;;) {
- Node t = tail;
- if (t == null) { // Must initialize
- if (compareAndSetHead(new Node()))
- tail = head;
- } else {
- //看这里
- node.prev = t;
- if (compareAndSetTail(t, node)) {
- t.next = node;
- return t;
- }
- }
- }
- }
新节点pre指向tail,tail指向新节点,这里后继指向前驱的指针是由CAS操作保证线程安全的。而cas操作之后t.next=node之前,可能会有其他线程进来。所以出现了问题,从尾部向前遍历是一定能遍历到所有的节点。
CAS 并不是万能的,CAS 更新有 ABA 问题。即 T1 读取内存变量为 A
,T2 修改内存变量为 B
,T2 修改内存变量为 A
,这时 T1 再 CAS 操作 A
时是可行的。但实际上在 T1 第二次操作 A
时,已经被其他线程修改过了。
对于 ABA 问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都 +1
;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。 AtomicStampedReference
便是使用版本号来解决ABA问题的。类似的还有 AtomicMarkableReference
, AtomicStampedReference
是使用 pair 的 int stamp
作为计数器使用, AtomicMarkableReference
的 pair 使用的是 boolean mark
。
线程进入读锁的前提条件:
线程进入写锁的前提条件:
而读写锁的特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入:读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
说明:Sync抽象类继承自AQS抽象类,Sync类提供了对ReentrantReadWriteLock的支持。
Sync类内部存在两个内部类,分别为HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要与读锁配套使用,其中,HoldCounter源码如下。
- static final class HoldCounter {
- int count = 0;
- // Use id, not reference, to avoid garbage retention
- final long tid = getThreadId(Thread.currentThread());
- }
-
- /**
- * ThreadLocal subclass. Easiest to explicitly define for sake
- * of deserialization mechanics.
- */
- static final class ThreadLocalHoldCounter
- extends ThreadLocal<HoldCounter> {
- public HoldCounter initialValue() {
- return new HoldCounter();
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。