AQS共享模式
AQS源码
之前的事独占锁的,这里介绍下共享模式会用到的Node节点的信号量,和方法
/**
* Node节点,出了多了一个PROPAGATE,其他也是CLH队列的方式差不多
*/
static final class Node {
/**
* 出现异常,中断引起的,需要废弃的node即节点. 中断一般是手动,程序异常通常是代码运行中问出题
* 在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待
* */
static final int CANCELLED = 1;
/**
* 传播 Semaphore 共享模式下
* 表示下一次共享式同步状态获取将会被无条件地传播下去
*/
static final int PROPAGATE = -3;
}
其他方法汇总:
acquireSharedInterruptibly(int arg) //获取共享锁如果中断抛异常
tryAcquireShared(int arg) // 尝试获取共享锁,protected的
doAcquireSharedInterruptibly(int arg) //获取共享锁的核心方法,后面介绍
setHeadAndPropagate(Node node, int propagate)和doReleaseShared()// 搭配实现共享模式下传播
protected boolean tryReleaseShared(int arg) //释放共享锁,protected的
doReleaseShared()和unparkSuccessor() //搭配,唤醒阻塞线程
并发工具类semaphore
// 构造器传入的2,标示state的锁资源初始为2.
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire(); // 获取锁,默认一次
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();// 释放锁
}
}).start();
}
获取锁
/**
* 获取共享锁如果中断抛异常
* @param arg
* @throws InterruptedException
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取锁,获取不到就入队
if (tryAcquireShared(arg) < 0)
// 入队
doAcquireSharedInterruptibly(arg);
}
/**
* 公平锁
* @param acquires
* @return
*/
protected int tryAcquireShared(int acquires) {
for (;;) {
//判断队列中是否有节点
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
//死循环要么没有state票据返回,要么有stata然后CAS成功放回。要么大于0,要么小于0
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 如果上面tryAcquireShared方法没有锁资源或者cas失败走这里
/**
* 入队,获取锁,阻塞等一系列操作,中间如果唤醒的阻塞线程有中断过则抛出异常
* @param arg
* @throws InterruptedException
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 构建共享模式节点,waitStatus=0
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 入队在第一个,再尝试获取一次锁,和reentrantLock一样
int r = tryAcquireShared(arg);
//>0 表示,刚获取完锁,但是锁资源还有,state>0
// =0 ,获取了锁,head节点需要移动到当前node.
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 和reentrantLock一样,阻塞,释放完锁后,线程从这里被唤醒,死循环的获取锁,直到获取到锁。waitStatus 0->-1 SIGNAL
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
/**
* 做了两件事:1 重新设置head节点 2 根据参数条件唤醒阻塞线程
* 2.1 if node节点waitStatus = signal 执行 signal->0 && 唤醒;if waitStatus=0 执行 0 ->PROPAGATE
* @param node
* @param propagate
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);//设置为头结点
/**
* 满足条件的尝试唤醒线程
* 调用者明确表示"传递"(propagate > 0), 或者h.waitStatus为PROPAGATE(被上一个操作设置)
* 并且
* 下一个节点处于共享模式或者为null。
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//唤醒后继节点,因为是共享模式,所以允许多个线程同时获取同步状态
doReleaseShared();
}
}
/**
*
* 修改信号量,释放共享锁锁
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果是signal状态,可以唤醒下一个线程
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 设置为PROPAGATE,满足上层要求的传递条件
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头结点发生变化,则继续循环。否则,退出循环。
if (h == head) // loop if head changed
break;
}
}
/**
* 唤醒阻塞线程
* @param node
*/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)//节点状态异常设置为0
compareAndSetWaitStatus(node, ws, 0);
/**
* 若后继结点为空,或状态为CANCEL(已失效),则从后尾部往前遍历找到最前的一个处于正常阻塞状态的结点
* 进行唤醒
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
释放锁
/**
* 释放共享锁,然后唤醒CLH队列中的线程
* @param arg
* @return
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 和上面一样,这里是主动释放,上面是检查释放
doReleaseShared();
return true;
}
return false;
}
// cas + 自旋 释放锁资源(把state加回去)
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
如何共享
在获取锁的时候需要检查锁资源,可能需要唤醒CLH队列中的阻塞线程去竞争锁资源,独占锁用acquireQueued方法,只需要入队,检查是否满足当前节点前驱是头节点就唤醒的条件,然后阻塞而在共享模式使用doAcquireSharedInterruptibly方法,满足当前节点前驱是头节点,唤醒锁后,还需要根据剩余锁资源数量去继续唤醒下一个线程。具体实现就靠setHeadAndPropagate的条件检查,和doReleaseShared设置满足唤醒条件的传播状态
总结
不同点:在独占模式下,锁资源只有一份,只有一个线程能获取锁资源,而共享模式下,可以允许多个线程获取锁资源。
相同点:每一份锁资源,同一时间只有一个线程能获取到,CAS实现