目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。
前言
本文主要是讲解Semaphore的概念,基本用法,使用场景,底层代码原理剖析。
一、Semaphore是什么?
按照源码注释,翻译成中文,就是以下内容:
- 计数信号量,从概念上讲,一个信号量维护一组许可。信号量存在许可,可以通过acquire获取它。每次release相当于增加一个许可。然而,没有实际的许可对象被使用。semaphore仅仅是保持可用的数量计数并采取相应行动。
- 信号量通常是用来限制可以访问一些资源(物理或逻辑)的线程数量。
- 使用一个信号量初始化,这样最多只有一个可用的许可证,可以作为一个互斥锁。这是通常被称为一个二进制信号量 ,因为它只有两个状态:一个可用的许可证,或零个可用的许可证。以这种方式使用时,二进制信号量拥有的特性:( 与java.util.concurrent.locks锁实现不同)可以由所有者以外的一个线程释放(如信号量没有所有权的概念)。这种特性在一些场景下,如死锁的复苏,是有用的。
- 一般来说,信号用于控制资源访问应该初始化公平,确保没有线程访问不到资源。有时,吞吐量的考虑优于公平的考虑。
二、使用方法
Semaphore 提供了一些方法:
方法 | 说明 |
---|---|
acquire() | 从这个信号量获得一个许可,阻塞直到有一个可用,或线程被中断打断。 |
release() | 返回一个许可证给信号量。 |
acquire(int permits) | permits个acquire() 。 |
release(int permits) | permits个release()。 |
isFair() | 如果这个信号量是公平锁,返回true |
acquire(int) , release(int) 两个方法支持同时获取,释放多个许河。需要知道风险:当没有使用公平锁时可能存在无限期推迟的情况。
三、应用场景
信号量就是一个计数器,应用很广泛。
3.1有界队列
我们构建一个有界队列,在队列满的时候希望阻塞而不是中断。那么信号量的大小就是队列的边界。
代码如下
package com.valley.juc.tools.semaphore;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
/**
* @author valley
* @date 2022/6/17
* @Description TODO
*/
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InterruptedException, NoSuchMethodException, InvocationTargetException {
String name =BoundedHashSet.class.getName();
Class<?> clazz=Class.forName(name);
Constructor constructor = clazz.getConstructor(int.class);
BoundedHashSet boundedHashSet= (BoundedHashSet) constructor.newInstance(100);
boundedHashSet.add(13);
boundedHashSet.remove(13);
}
public BoundedHashSet(int bound) {
// 初始化信号量的值,作为set容器的边界
this.set = Collections.synchronizedSet(new HashSet<T>());
this.sem = new Semaphore(bound);
}
/**
* 如果添加成功,则信号量的可用值减少一个;否则,被acquire占用的信号量要释放。
* @param o
* @return
* @throws InterruptedException
*/
public boolean add(T o) throws InterruptedException{
sem.acquire();
boolean wasAdded = false;
try{
wasAdded = set.add(o);
return wasAdded;
} finally {
if (!wasAdded) {
sem.release();
}
}
}
/**
* 容器删除一个数据是,要释放对应的信号量
* @param o
* @return
*/
public boolean remove(Object o){
final boolean remove = set.remove(o);
if (remove) {
sem.release();
}
return remove;
}
}
3.2资源池
注意,调用acquire时,为了不影响资源被返回到池中,没有同步锁持有。但是封装的信号同步访问池需要限制,独立于任何同步需要保持池本身的一致性。synchronized修饰两个方法,共用一把锁,保持了池本身的一致性。
package com.valley.juc.tools.semaphore;
import java.util.concurrent.Semaphore;
/**
* @author valley
* @date 2022/6/17
* @Description TODO
*/
public class Pool {
private static final int MAX_AVAILABLE = 10;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Pool pool =Pool.class.newInstance();
try {
System.out.println(pool.getItem());
pool.putItem(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = new Object[]{1,2,3,4,5,6,7,8,9,10};
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
四、源码剖析
Semaphore 的源码在JUC并发工具中,相对复杂点:
- 使用了继承AQS的静态内部类Sync,也就是底层基于AbstractQueuedSynchronizer 来实现的
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
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;
}
}
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
/**
* NonFair version
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
/**
* Fair version
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
- Semaphore 构造函数中包含指定许可的数量和是否是公平锁;
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
- acquire()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
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) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- release
public void release() {
sync.releaseShared(1);
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
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;
}
}
五、和其他工具比较
- Semaphore,CountDownLatch与CyclicBarrier区别
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,Semaphore主要是计数。
总结
主要讲解Semaphore,后面重点讲AQS。