【并发工具类-协作】-Semaphore限流器

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();
});
发布了34 篇原创文章 · 获赞 0 · 访问量 1089

猜你喜欢

转载自blog.csdn.net/qq_42634696/article/details/105089126