AQS共享模式的使用
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class SemaphoreDemo {
//设定信号数量
private static final Semaphore SEMAPHORE = new Semaphore(3);
//每次获取的许可数
private static final int PERMITS = 1;
//线程体
static class testThread implements Runnable{
@Override
public void run() {
try {
//获取许可
SEMAPHORE.acquire(PERMITS);
System.out.println(printCurrent() + ":" + Thread.currentThread().getName() + "进来了");
//等待,继续持有许可,不会释放
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可
SEMAPHORE.release(PERMITS);
}
}
private String printCurrent(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss SSS");
return simpleDateFormat.format(new Date());
}
public static void main(String[] args) {
ThreadPoolExecutor executor = null;
try{
int corePoolSize = 4;
int maxPoolsize = 4;
long keepLiveTime = 5;
TimeUnit unit = TimeUnit.SECONDS;
ThreadFactory factory = new NameThreadFactory();
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(5);
executor = new ThreadPoolExecutor(corePoolSize,maxPoolsize,keepLiveTime,unit,blockingQueue,factory);
executor.execute(new testThread());
executor.execute(new testThread());
executor.execute(new testThread());
executor.execute(new testThread());
}finally {
assert executor != null;
executor.shutdown();
}
}
static class NameThreadFactory implements ThreadFactory{
private final AtomicInteger threadId = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"TestThread"+threadId.getAndIncrement());
}
}
}
}
AQS共享模式源码解析
独占模式和共享模式最大的不同就是在同一时刻能否有多个线程获取同步状态。独占模式,只有一个线程获取同步状态并执行。共享模式在获取资源后,多个线程共同执行。
acquireShred()方法
此方法是共享模式下线程获取资源的顶层入口。获取成功则直接返回。失败则进入等待队列,并自旋直到获取资源为止。
/**
*以共享模式获取资源,忽略中断
*/
public final void acquireShared(int arg) {
//该方法只有一个分支,判断是否能获取到共享资源
//能获取到则返回正数,否则,返回负数
if (tryAcquireShared(arg) < 0)
//没有获取到共享资源,将执行doAcquireShred()方法
doAcquireShared(arg);
}
tryAcquireShared()方法
/**
*以共享模式尝试获取资源,交给子类实现
*返回小于0,说明获取资源失败
*返回0说明当前线程获取同步状态成功其他线程无法获取也就不需要唤醒它的后继节点进行传播
*返回大于0说明当前线程获取同步状态后要唤醒它的后继节点让其他线程也尝试去获取同步状态
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
比如CountDownLatch,用state==0表示共享资源的状态。
public class CountDownLatch {
//CountDownLatch的内部类Sync是AQS的子类
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//内部类Sync实现AQS的tryAcquireShared()方法
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
doAcquireShared()方法
如果tryAcquireShared()方法返回小于0,说明获取资源失败,则通过doAcquireShared()方法将线程加入等待队列
/**
*加入等待队列
*以共享非中断获取同步状态
*/
private void doAcquireShared(int arg) {
//自旋加入队尾,与独占模式相似。只是传入参数不同
//独占模式下传入参数Node.EXCLUSIVE
//共享模式下传入参数Node.SHARED
final Node node = addWaiter(Node.SHARED);
//标记是否失败
boolean failed = true;
try {
//标记是否中断
boolean interrupted = false;
//自旋
for (;;) {
//获取node节点的前驱节点
final Node p = node.predecessor();
//判断节点的前驱节点是否为头节点,如果是,尝试获取共享资源
if (p == head) {
//尝试获取共享资源
int r = tryAcquireShared(arg);
//如果r>=0表示获取共享资源成功
if (r >= 0) {
//将当前节点设置为头节点,检查后继节点是否在共享模式下等待
setHeadAndPropagate(node, r);
//原头节点的next引用置为null,方便JVM对头节点进行GC
p.next = null; // help GC
//如果线程被中断
if (interrupted)
//维护中断状态
selfInterrupt();
//标记failed为false
failed = false;
//返回
return;
}
}
//检查是否需要阻塞线程,判断中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate()方法
独占模式获取同步状态后,直接返回中断状态,结束流程。共享模式则调用setHeadAndPropagate()方法传播唤醒的动作
/**
*成为头节点,唤醒后继节点
*/
private void setHeadAndPropagate(Node node, int propagate) {
//获取队列的头节点
Node h = head;
//把当前节点设为头节点
setHead(node);
/*
* 有三种情况可执行唤醒操作
*1.propagate>0,代表后继节点需要唤醒
*2.原头节点为null或者ws<0
*3.新的头节点为null或者新的头节点的ws<0
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//找到当前节点的后继节点
Node s = node.next;
//s=null或者s是共享模式,调用doReleaseShared()方法唤醒后继节点的线程
if (s == null || s.isShared())
doReleaseShared();
}
}
doReleaseShared()方法
doReleaseShared()方法释放满足条件的后继节点
/**
*共享模式的释放操作
*/
private void doReleaseShared() {
/*
* 自旋释放后继节点
*/
for (;;) {
//自旋将动态的获取队列的头节点
Node h = head;
//如果头节点不为空且头节点不等于尾节点(即队列中至少有两个节点)
if (h != null && h != tail) {
//获取头节点的waitStatus
int ws = h.waitStatus;
//如果ws状态等于SIGNAL
if (ws == Node.SIGNAL) {
//变量h的waitStatus通过CAS设置为初始状态0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
//如果CAS失败,执行continue进入下一次循环
continue;
//如果CAS成功,唤醒后继节点
unparkSuccessor(h);
}
//如果h的ws=0,就把h的ws设为PROPAGATE表示可以向后传播唤醒
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//执行下一次循环
continue;
}
//自旋跳出条件,head不变则跳出自旋,head变化则一直自旋
//如果头节点没有发生改变,表示设置完成,可以跳出循环
//如果头节点发生了变化,可能被唤醒的其他节点重新设置了头节点
//这样头节点发生了改变,要进行重试,保证可以传播信号
if (h == head)
break;
}
}
小结
- tryAcquireShared()尝试获取资源,成功则直接返回;失败则通过doAcquireShared()进入等待队列park(),直到被unpark()/interrupt()并成功获取到资源才返回。整个过程忽略中断。
- 跟独占模式acquire()的流程大同小异,只是多了自己拿到资源后,还会去唤醒后继节点的操作。
releaseShared()方法
releaseShared()释放共享锁,是共享模式释放锁的顶层入口。它会释放指定的共享资源,如果释放成功就会去唤醒等待线程。
/**
*共享模式释放资源
*/
public final boolean releaseShared(int arg) {
//尝试释放共享资源
if (tryReleaseShared(arg)) {
//释放资源成功后,执行doReleaseShared()方法
//同acquireShared()方法调用doReleaseShared()方法类似
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared()方法
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
比如CountDownLatch的tryReleaseShared(),就是自旋state减1,state==0则完全释放锁返回true。
/**
*CountDownLatch内部类Sync继承AQS重写tryReleaseShared()方法
*/
protected boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
//获取state
int c = getState();
//判断state是否为0
if (c == 0)
//返回false
return false;
//state减一
int nextc = c-1;
//CAS操作将nextc赋值给c
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
小结
- releaseShared()尝试释放资源,如果释放失败,返回false
- 如果返回成功,doReleaseShared()方法释放后继节点