目录
AbstractQueuedSynchronizer 的模板方法
1. 什么是AQS
AQS ( Abstract Queued Synchronizer )是一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架
2. JAVA类图 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3683e104ccfcc00061bbe908d2fa4a2b.png)
由上图可见, AQS主要的实现方事sync -> nofairSync 和 FairSync
在ReentrantLock , ReentrantReadWriteLock, COuntDownLatch中引用了Sync的实现
static final class Node {
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 标识线程已处于结束状态
static final int CANCELLED = 1;
// 等待被唤醒状态
static final int SIGNAL = -1;
// 条件状态
static final int CONDITION = -2;
// 在共享模式中使用表示获得的同步状态会被传播
static final int PROPAGATE = -3;
// 等待状态, 存在CANCELLED、SIGNAL、CONDITION、PROPAGATE 4种
volatile int waitStatus;
// 同步队列中前驱结点
volatile Node prev;
// 同步队列中后继结点
volatile Node next;
// 请求锁的线程
volatile Thread thread;
// 等待队列中的后继结点
Node nextWaiter;
//判断是否为共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
//获取前驱结点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//.....
}
AbstractQueuedSynchronizer waitStatus 取值
常量 | 值 | 意义 |
---|---|---|
CANCELLED | 1 | 同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,节点终极状态 |
SIGNAL | -1 | 等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行 |
CONDITION | -2 | 该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁 |
PROPAGATE | -3 | 在共享模式中,该状态标识结点的线程处于可运行状态 |
0 | 0 | 初始状态 |
AbstractQueuedSynchronizer 的模板方法
AbstractQueuedSynchronizer 类作为抽象的基础框架类,通过定义模板方法的方式提供了一套实现锁的模板,其最基本的锁实现方式需要子类复写模板:
protected boolean tryAcquire(int arg); // 获取独占锁
protected boolean tryRelease(int arg); // 释放独占锁
protected int tryAcquireShared(int arg); // 获取共享锁
protected boolean tryReleaseShared(int arg); // 释放共享锁
protected boolean isHeldExclusively(); // 判断是否持有独占锁
3. 主要方法以及主要流程
-
获取锁 --- 独占锁
public final void acquire(int arg) { // tryAcquire子类自己实现 if (!tryAcquire(arg) && // 获取锁 // 添加独占 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
/* 挂起当前线程: 1. pre节点如果status是signal, 说明会通知下一个节点 2. 如果pre节点取消了, 那么就会一直往前找 找到那个signal的节点 3. pre节点已经获取了当前的状态 */ final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋获取锁 for (;;) { // 获取前继节点 final Node p = node.predecessor(); // 子类自己实现获取锁 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 获取失败之后, 自己park下, 然后告诉前面的节点需要通知自己 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) // 如果自旋一直失败, 就把这个节点干掉 cancelAcquire(node); } } // 一直找到那个能通知自己的节点 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 把pre节点的status换成signal可以唤醒下一个节点的状态 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
-
释放锁 -- 独占锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 判断有没有被释放
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
//表明该锁被重入了多次,当前并没有解锁
return false;
}
// 回到ReentrantLock看tryRelease方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否完全释放锁
boolean free = false;
// 其实就是重入的问题,如果c==0,也就是说没有嵌套锁了,可以释放了,否则还不能释放掉
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 唤醒后继节点
// 从上面调用处知道,参数node是head头结点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 如果head节点当前waitStatus<0, 将其修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
// 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从后往前找,仔细看代码,不必担心中间有节点取消(waitStatus==1)的情况
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}
4. 如何实现带过期时间的锁
// 计算一个最终过期时间
final long deadline = System.nanoTime() + nanosTimeout;
// 自旋校验
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
5. 如何实现读写锁 - 独占锁和共享锁
-
进入读锁的前提条件:
没有其他线程的写锁;
没有写请求,或者有写请求,但是调用线程和持有锁线程是同一个。
-
进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
-
读写锁的特性
- 公平性选择:支持 非公平(默认) 和公平的锁获取方式,吞吐量还是非公平优先
- 可重入: 读和写锁都支持重入。
- 读锁线程获取读锁后,还能够获取读锁。
- 写锁在获取线程后,还能获取写锁,同时也可以获取读锁。
- 锁降级: 获取写锁时,然后获取读锁,然后释放写锁的顺序,写锁能降级为读锁。(提高并发性能)
- 上面写了独占锁的
- 下面是共享锁的
-
获取锁 -- 共享锁
-
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { // 这个方法是主要的方法 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } // 如果成功则唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点,然后进入临界区,否则继续挂起等待 private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below //设置新的头节点,即把当前获取到锁的节点设置为头节点 //注:这里是获取到锁之后的操作,不需要并发控制 setHead(node); // 如果节点是shard,那么就要传递下去 //这里意思有两种情况是需要执行唤醒操作 //1.propagate > 0 表示调用方指明了后继节点需要被唤醒 //2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; // isShared方法是在addWaiter的时候初始化出来的 //如果当前节点的后继节点是共享类型获取没有后继节点,则进行唤醒 //这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒 if (s == null || s.isShared()) doReleaseShared(); } }
唤醒后继节点
private void doReleaseShared() { for (;;) { //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了 //其实就是唤醒上面新获取到共享锁的节点的后继节点 Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; //表示后继节点需要被唤醒 if (ws == Node.SIGNAL) { //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; //执行唤醒操作 unparkSuccessor(h); } //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } //如果头结点没有发生变化,表示设置完成,退出循环 //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试 if (h == head) break; } }