一.信号量
Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
二.信号量模型
概况为:一个计数器,一个等待队列,三个方法。在信号模型里,计数器和等待队列对外是透明
,所以只能通过信号模型提供的三个方法来访问它们,这三个方法分别是:init()、down()、up()。
这三个方法详细语义:
- init():设置计数器初始值。
- down():计数器的值减1;如果此时计数器的值小于0,则当前线程将阻塞,否则当前线程可以继续执行。
- up():计数器的值加1;如果此时计数器的值小于或者等于0,则唤醒等待队列中一个线程,并将其从等待队列中移除。
这里提到的 init()、down() 和 up() 三个方法都是原子性的,并且这个原子性是由信号量模型的实现方保证的。在 Java SDK 里面,信号量模型是由 java.util.concurrent.Semaphore 实现的,Semaphore 这个类能够保证这三个方法都是原子操作。
// Java SDK 并发包里,down() 和 up() 对应的则是 acquire() 和 release()。
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
}
}
}
三.使用信号量
3.1 示例-累加器
count+=1操作是个临界区,只允许一个线程执行,也就是说要保证互斥。
public class SemaphoreTest {
static int count;
static final Semaphore s = new Semaphore(1);
static void addOne(){
try {
s.acquire();
count +=1;
}catch (Exception e){
e.printStackTrace();
}finally {
s.release();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(()->{
SemaphoreTest.addOne();
});
Thread t2 = new Thread(()->{
SemaphoreTest.addOne();
});
Thread t3 = new Thread(()->{
SemaphoreTest.addOne();
});
t1.start();
t2.start();
t3.start();
try {
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(SemaphoreTest.count);
}
}
3.2 示例-限流器
Semaphore 有个功能是Lock不容易实现,就是Semaphore可以允许多个线程访问一个临界区。
例如:对象池,线程池,都会用到类似功能。
/**
* 对象池,指的一次性创建出N个对象,之后所有线程重复利用这N个
* 对象,当然对象在被释放前,也是不容许其他线程使用。 *
* 对象的互斥性
*/
public class ObjPool<T,R> {
//用信号量实现限流器
final List<T> pool;
//构造函数
final Semaphore sem;
public ObjPool(T[] tArray){
pool = new Vector<T>(){};
int size = tArray.length;
for (int i = 0; i < tArray.length; i++) {
pool.add(tArray[i]);
}
sem = new Semaphore(size);
}
R exec(Function<T,R> func) throws InterruptedException {
T t = null;
try {
sem.acquire();
t = pool.remove(0);
return func.apply(t);
}finally {
pool.add(t);
sem.release();
}
}
public static void main(String[] args) {
String[] mess = new String[10];
for (int i = 0; i < 10; i++) {
mess[i] = "obj_"+i;
}
ObjPool<String, String> objPool = new ObjPool<>(mess);
for (int i = 0; i < 100; i++) {
Thread t1= new Thread(()->{
try {
objPool.exec(t -> {
System.out.println("当前线程id:"+Thread.currentThread().getId()+",当前获取到的对象:"+t);
return t;
});
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
参考
《Java并发编程实战》
公众号
微信公众号(bigdata_limeng)