Java并发工具包一---Semaphore
1、什么是Semaphore?
Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目(就是对一个资源的访问并发量最多有多高),底层依赖AQS的状态State,是在生产当中比较常用的一个工具类。
2、Semaphore的方法
2.1、构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
permits:permits参数代表同一时间访问资源的线程数量,也就是state。
fair:fair参数代表公平性,如果设置为true的话,下次执行的线程将会是等待最久的线程。
2.2、重要方法
public void acquire() throws InterruptedException
public void release()
tryAcquire(long timeout, TimeUnit unit)
acquire():表示获取资源,如果它有permits参数的话,代表一个线程获取一次资源时需要给state减去几,就是2.1中的state。
release():表示释放资源,如果它有permits参数的话,代表一个线程获取一次资源时需要给state加上几,就是2.1中的state。
tryAcquire:尝试获取,可以设置超时时间。举例如下:
semaphore.tryAcquire(500,TimeUnit.MILLISECONDS)
3、Semaphore简单应用
public class SemaphoreRunner {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i=0;i<10;i++){
new Thread(new Task(semaphore,"du+"+i)).start();
}
}
static class Task extends Thread{
Semaphore semaphore;
public Task(Semaphore semaphore,String tname){
super(tname);
this.semaphore = semaphore;
}
public void run() {
try {
//获取公共资源
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+":aquire() at time:"+System.currentTimeMillis());
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
当我们执行这段代码时就会发现,每一次在访问资源时由于semaphore做了控制所以同一时间最多只有两个线程获取到资源。
4、源码简析
4.1、acquire() 方法
先看个大体流程图,如下所示:
进入到源码之后,我们会走到这个方法:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 创建一个共享节点并入队
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 返回当前节点的前驱节点,用前驱节点判断
final Node p = node.predecessor();
// 如果前驱节点时头节点的话
if (p == head) {
// 尝试获取共享资源,走4.1.1
int r = tryAcquireShared(arg);
// 如果r >= 0,说明获取成功
if (r >= 0) {
// 唤醒头节点并把节点往后挪一个
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// shouldParkAfterFailedAcquire判断节点是否损坏,如果是,则剔除
// parkAndCheckInterrupt 阻塞节点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 如果出现坏的节点,将会在这里被剔除
if (failed)
cancelAcquire(node);
}
}
4.1.1、tryAcquireShared
protected int tryAcquireShared(int acquires) {
// 死循环加上cas保证如果有资源使得线程一定要获取到资源再退出
for (;;) {
// 如果在队列中,直接返回-1
if (hasQueuedPredecessors())
return -1;
// 获取资源当前的状态值
int available = getState();
// 减去当前线程需要的资源值
int remaining = available - acquires;
// 要么不成功,将负数返回回去
// 要么多次遍历成功
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
4.2、release()
先看个大体流程图,如下所示:
我们点击release方法之后,会进入到下面这个方法
public final boolean releaseShared(int arg) {
// 先还回信号量
// tryReleaseShared,走4.2.1
if (tryReleaseShared(arg)) {
// 判断节点是否可以唤醒获取资源并将队列往后挪,并且释放资源
// doReleaseShared,走4.2.2
doReleaseShared();
return true;
}
return false;
}
4.2.1、tryReleaseShared()
还回信号量
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取当前资源的state值
int current = getState();
// 将即将要释放的资源值加上去
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
// 将加了之后的值通过cas算法保证原子性将值替换过去
if (compareAndSetState(current, next))
return true;
}
}
4.2.2、doReleaseShared()
判断后续节点是否可以唤醒获取资源并将队列往后挪,并且释放当前节点资源
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果是释放信号量走到这里的话,前面获取信号量时,node的state值应该是SIGNAL,说明可以唤醒
if (ws == Node.SIGNAL) {
// 这块强行将waitState值转换为0,因为马上要释放锁了
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 释放锁,并且将头节点往后挪
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 在这块退出
if (h == head) // loop if head changed
break;
}
}