(1)信号灯,或者说限流器
Semaphore由迪杰斯特拉提出的,一直被使用,直到管程被提出,
首先是信号量模型
在信号量中,可以简述为 一个计数器,一个等待队列,三个方法
计数器和等待队列是必要的,透明的,信号量模型的三个方法为 init() down() up()
init为,设置计数器的初始值
down()将计数器的值进行减一的操作,如果小于了0,将线程阻塞
up() 计数器的值加一操作,如果仍然小于或者等于0,唤醒一个等待队列中的线程
这三个方法的值的设置,都是保证了其原子性的基础上
接下来是一个简单的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
}
}
}
down和up最早被称为P和V操作,又名PV原语,
在Java中信号量的实现在Java.util.concurrent.Semaphore实现的
其中的对应方法为,
down() -> acquire()
up() -> release()
如何去使用信号量呢,很简单,就是在累加器上,通过控制其中的一个信号量,在一个临界区之间徘徊,保证线程的运行和阻塞之间的切换
在进入临界区之前进行一下down() 在出区的时候进行一下up()
static int count;
//初始化信号量
static final Semaphore s = new Semaphore(1);
//用信号量保证互斥
static void addOne () {
s.acquire();
try {
count += 1;
} finally {
s.release();
}
}
其内部原理很简单,因为对信号量的操作是一个原子操作,那么只能有一个线程能对信号量操作
那么对于A线程,线程大于等于0,继续执行,对于线程B,线程小于0,进入阻塞状态
对于A线程,完成后,将计数器加到0,进行唤醒一个等待队列中的线程,由此保证了互斥性
程将计数器减为0
快速实现一个限流器
对于Semaphore,相比起Lock的功能,提供了一个新的思路,那就是允许 多个线程访问一个临界区
常见的就是各类池化的资源,也是一次性创建出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(); }); |
上面就是利用了信号量来实现的一个限流器