Semaphore(英文读:sem破)限流器
1.信号量模型
信号量模型简单地概括为:一个计数器,一个等待队列,三个原子操作方法,init(),up(),down()。
这三个方法的语义具体如下:
- init():设置计数器的初始值。
- down():计数器的值 -1。如果-1后此时计数器的值<0,当前线程被阻塞,放入等待队列中,否则当前线程可以继续执行。
- up():计数器的值 +1。如果+1后此时计数器的值<=0,唤醒等待队列中的一个线程,并将其从等待队列中移除。否则直接执行当前线程。
2.使用信号量Semaphore实现互斥
static int count;
static final Semaphore s = new Semaphore(1);//初始化信号量
//用信号量保证互斥
static void addOne() {
s.acquire();//原子操作down()方法
try {
count+=1;
} finally {
s.release();//原子操作up()方法
}
}
假设线程T1和线程T2同时访问addOne方法,当它们同时调用acquire方法,因为acquire是原子方法,所以只能一个线程(假设T1)把信号量的计数器-1=0,线程T2把信号量计数器-1= -1,对于线程T1计数器没有小于0,所以直接执行,对于线程T2计数器小于0,当前线程被阻塞,放到等待队列中。
当线程T1执行release方法时,也就是up方法,当前计数器是-1然后+1=0,就会从等待队列中唤醒线程T2,T2才有机会执行临界区(try{})的的机会,这样就能保证互斥性。
3.快速实现一个限流器
上面我们用限流器实现了互斥锁功能,当然它还有更重要的功能:Semaphore允许多个线程访问一个临界区。
现实生活中,还有这种需求呢? 有!就是各种池化资源,连接池,对象池,线程池等,就比如数据库连接池,在同一时刻,允许多个线程同时使用连接池,当然!在一个连接在被释放之前,不能被其他线程使用。
快速实现一个对象池的限流器。(所谓对象池,就是一次性创建出N个对象,之后所有的线程重复利用这些对象,当然对象被释放之前其他线程是不能获取的)
解决方法呢?其实你也想到了就是信号量模式,我们只要把初始化的计数器为1改成N即可。下面就是对象池的实例代码。
class ObjPool<T, R> {
final List<T> pool;
// 用信号量实现限流器
final Semaphore sem;
// 构造函数
ObjPool(int size, T t){
pool = new Vector<T>(){};//这里要使用线程安全的容器,防止多个线程同时对list中的对象读写。
for(int i=0; i<size; i++){
pool.add(t); //实际开发中这里我们要添加真实的每一个对象。
}
sem = new Semaphore(size);
}
//利用对象池的对象,调用func(就是调用exec方法,同时传入该方法一个外部实现的方法给这个方法,需要调用外部方法时,就apply即可)
R exec(Function<T,R> func) {
T t = null;
sem.acquire();
try {
t = pool.remove(0);
return func.apply(t);//这里执行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();
});