第二部分:并发工具类16->Semaphore:如何快速实现一个限流器
1.Semaphore
信号量,类比红绿灯
编程中,线程能不能执行,看信号量是否允许
2.信号量模型
计数器+等待队列+三个方法
计数器和等待队列对外是透明的,只能通过提供的三个方法(init,down,up)来访问它们
init()设置计数器的初始值
down()计数器值减1,计数器当前值小于0,等钱线程被阻塞,否则当前线程可以继续执行
up()计数器值加1,计数器的值小于或者等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除
init,down,up三个方法都是原子性的,通过信号量模型实现方保证
java.util.concurrent.Semaphore实现
信号量模型
class Semaphore{
// 计数器
int count;
// 等待队列
Queue queue;
// 初始化操作
Semaphore(int c){
this.count=c;
}
//
void down(){
this.count--;
if(this.count<0){
//将当前线程插入等待队列
//阻塞当前线程
}
}
void up(){
this.count++;
if(this.count<=0) {
//移除等待队列中的某个线程T
//唤醒线程T
}
}
}
sdk并发包里,down和up对应的是acquire和release
3.如何用信号量
count += 1,临界区,只允许一个线程执行,
类似互斥锁,进入临界区之前执行down操作,退出临界时执行up操作
static int count;
//初始化信号量
static final Semaphore s
= new Semaphore(1);
//用信号量保证互斥
static void addOne() {
s.acquire();
try {
count+=1;
} finally {
s.release();
}
}
多线程T1,T2,同时调用acquire时,acquire是原子操作,只有一个线程能将信号量的计数器变为0,线程T2将计数器减为-1
对于线程T1,信号量里面计数器值是0,大于等于0,线程T1会继续执行,执行count += 1。
对于线程T2,信号量里面计数器值是-1,小于0,按照信号量模型描述,T2线程被阻塞。
T1执行完count+=1操作有,执行release()操作,信号量计数器值加1,变为0,小于等于0,对应信号量模型up描述,T2线程被唤醒,T2在T1执行完代码后获取了执行临界区的机会,保证了互斥性
4.快速实现限流器
sdk里面有互斥锁了,干嘛还用semaphore呢
sempahore可以允许多个线程访问一个临界区
限流:不允许多与N个线程同时进入临界区
信号量计数器,设置为1,表示只允许一个线程进入临界区,设置为n,表示同时允许n个线程进入临界区
对象池的伪代码
class ObjPool<T, R> {
final List<T> pool;
// 用信号量实现限流器
final Semaphore sem;
// 构造函数
ObjPool(int size, T t){
pool = new Vector<T>(){};
for(int i=0; i<size; i++){
pool.add(t);
}
sem = new Semaphore(size);
}
// 利用对象池的对象,调用func
R exec(Function<T,R> func) {
T t = null;
sem.acquire();
try {
t = pool.remove(0);
return func.apply(t);
} finally {
pool.add(t);
sem.release();
}
}
}
// 创建对象池
ObjPool<Long, String> pool =
new ObjPool<Long, String>(10, 2);
// 通过对象池获取t,之后执行
pool.exec(t -> {
System.out.println(t);
return t.toString();
});
原创:做时间的朋友