目录
早在1965就提出了信号量作为解决并发编程的解决方案,直到1980年之后才使用管程模型来解决并发问题(Java选择了MESA管程模型),并且管程能实现的功能使用信号量都能实现,基本所有的编程语言都支持信号量的方式,而Java中juc Semaphore就是该实现。作为非科班出生,自己认为了解并发编程的历史非常的重要。
信号量模型
一个计数器,一个等待队列,三个方法。信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down() 和 up()
init():设置计数器的初始值。
down():计数器的值减 1;如果此时计数器的值小于 0,则当前线程将被阻塞,否则当前线程可以继续执行。
up():计数器的值加 1;如果此时计数器的值小于或者等于 0,则唤醒等待队列中的一个线程,并将其从等待队列中移除。
juc Semaphore结构
Semaphore与ReentrantLock一样的结果,属性Sync实现自AQS,都实现了公平和非公平锁两种,但是刚好一个是AQS的独占模式,Semaphore是共享模式。
public class Semaphore implements java.io.Serializable {
/** 默认构造实现非公平锁 */
private final java.util.concurrent.Semaphore.Sync sync;
public Semaphore(int permits) {
sync = new java.util.concurrent.Semaphore.NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
}
无论公平锁非公平锁,初始化时都要设置初始值(即上面的init方法需要设置初始信号量值),而当前我们设置的初始化,直接设置为了AQS的state变量。
static final class NonfairSync extends Sync { // Semaphore 非公平锁
NonfairSync(int permits) {
super(permits); // super就是Semaphore.Sync
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits); // 父类Semaphore Sync
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private volatile int state;
protected final void setState(int newState) {
state = newState; // 父类AQS,直接将值赋给state
}
}
再看看信号量模型的up和down方法,在Semaphore中的实现:
up -> acquire* -> LockSupport#park挂起线程
直接调用AQS的acquireSharedInterruptibly方法,参见:并发编程理论 - AQS源码分析#acquireShared【获取共享锁,阻塞剩余资源数的线程】
// 每次加1
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 可以自定义加的数
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException(); // 我们自定义的步数只能是整数
sync.acquireSharedInterruptibly(permits);
}
down -> release* -> LockSupport#unpark唤醒线程执行任务
直接调用AQS的releaseShared方法,参见:并发编程理论 - AQS源码分析#releaseShared【释放共享锁,唤醒管程的可用节点线程】
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException(); // 我们自定义的步数只能是整数
sync.releaseShared(permits);
}
使用Semaphore实现限流器
限流器就是共享模式的实现,比如同时允许3个线程执行任务,前提是有任务的情况下,如果没有任务则需要将线程挂起,否则唤醒对应的线程。
/**
* 使用{@link Semaphore} 实现一个限流器
* 即多线程的并发的情况下,只允许指定长度的个数执行任务,其余的排队
*
* .......................省略.........................
* 当前线程{test-thread-93}获取到的任务为:kevin test 2
* 当前线程{test-thread-94}获取到的任务为:kevin test 3
* 当前线程{test-thread-95}获取到的任务为:kevin test 4
* 当前线程{test-thread-96}获取到的任务为:kevin test 5
* 当前线程{test-thread-97}获取到的任务为:kevin test 6
* 当前线程{test-thread-98}获取到的任务为:kevin test 7
* 当前线程{test-thread-99}获取到的任务为:kevin test 8
* 当前线程{test-thread-100}获取到的任务为:kevin test 9
*
* @author kevin
* @date 2020/7/30 22:32
* @since 1.0.0
*/
public class SemaphoreLimiter<T, R> {
private final List<T> pool;
private final Semaphore semaphore;
/**
* 限流器构造
* @param t 限流类型
*/
public SemaphoreLimiter(T[] t) {
int size = t.length;
this.pool = new Vector<T>(){};
for (int i = 0; i < size; i++) {
pool.add(t[i]);
}
this.semaphore = new Semaphore(size);
}
/**
* 执行任务
* @param function 功能
* @return
* @throws InterruptedException
*/
R execute(Function<T, R> function) throws InterruptedException {
T t = null;
semaphore.acquire();
try {
t = pool.remove(0);
return function.apply(t);
} finally {
pool.add(t);
semaphore.release();
}
}
public static void main(String[] args) throws InterruptedException {
String[] str = new String[10];
for (int i = 0; i < str.length; i++) {
str[i] = "kevin test " + i;
}
SemaphoreLimiter<String, String> pool = new SemaphoreLimiter<String, String>(str);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100,
0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), new DefaultThreadFactory("test"));
for (int i = 0; i < 100; i++) {
threadPoolExecutor.execute(() -> {
try {
pool.execute(t -> {
System.out.println("当前线程{" + Thread.currentThread().getName() + "}获取到的任务为:" + t);
return t;
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
}
}
}