Semaphore-信号量

信号量模型和管程是等价的,信号量的模型更加简单。
image.png

  1. 计数器
  2. 等待队列

3个方法

  1. init 初始化 初始化计数器的值
  2. down计数器的值减 1 ,如果此时计数器的值小于 0,则当前线程将被阻塞,否则当前线程可以继续执行。
  3. up计数器的值加1,如果此时计数器的值小于 或者等于0 ,则唤醒线程,并从等待队列中将其移除。

什么意思呢?就是说,如果down操作完后,在运行前先判断计数器计数器小于0 了,我们就阻塞,大于0,我们就执行。在up操作完后,再次判断计数器,如果计数器大于0 ,没有阻塞,小于或等于0我们要唤醒线程。

代码如下:

  1. class Semaphore{
  2. // 计数器
  3. int count;
  4. // 等待队列
  5. Queue queue;
  6. // 初始化操作
  7. Semaphore(int c){
  8. this.count=c;
  9. }
  10. //
  11. void down(){
  12. this.count--;
  13. if(this.count<0){
  14. //将当前线程插入等待队列
  15. //阻塞当前线程
  16. }
  17. }
  18. void up(){
  19. this.count++;
  20. if(this.count<=0) {
  21. //移除等待队列中的某个线程T
  22. //唤醒线程T
  23. }
  24. }
  25. }

Java中的信号量

Java中的信号量是Semaphore,acquire() 就是信号量里的 down() 操作,release() 就是信号量里的 up() 操作。
代码示例:

  1. static int count;
  2. //初始化信号量
  3. static final Semaphore s
  4. = new Semaphore(1);
  5. //用信号量保证互斥
  6. static void addOne() {
  7. s.acquire();
  8. try {
  9. count+=1;
  10. } finally {
  11. s.release();
  12. }
  13. }

限流器

如果仅仅是为了实现锁的功能,lock那一套已经够了,那为什么还要提供Semaphore呢?
Semaphore 可以允许多个线程访问一个临界区
实际问题:
比较常见的需求就是我们工作中遇到的各种池化资源,例如连接池、对象池、线程池等等。其中,你可能最熟悉数据库连接池,在同一时刻,一定是允许多个线程同时使用连接池的,当然,每个连接在被释放前,是不允许其他线程使用的。

实例
在工作中也遇到了一个对象池的需求。所谓对象池呢,指的是一次性创建出 N 个对象,之后所有的线程重复利用这 N 个对象,当然对象在被释放前,也是不允许其他线程使用的。对象池,可以用 List 保存实例对象,这个很简单。但关键是限流器的设计,这里的限流,指的是不允许多于 N 个线程同时进入临界区。那如何快速实现一个这样的限流器呢?这种场景,我立刻就想到了信号量的解决方案。信号量的计数器,在上面的例子中,我们设置成了 1,这个 1 表示只允许一个线程进入临界区,但如果我们把计数器的值设置成对象池里对象的个数 N,就能完美解决对象池的限流问题了。下面就是对象池的示例代码。

  1. class ObjPool<T, R> {
  2. final List<T> pool;
  3. // 用信号量实现限流器
  4. final Semaphore sem;
  5. // 构造函数
  6. ObjPool(int size, T t){
  7. pool = new Vector<T>(){};
  8. for(int i=0; i<size; i++){
  9. pool.add(t);
  10. }
  11. sem = new Semaphore(size);
  12. }
  13. // 利用对象池的对象,调用func
  14. R exec(Function<T,R> func) {
  15. T t = null;
  16. sem.acquire();
  17. try {
  18. t = pool.remove(0);
  19. return func.apply(t);
  20. } finally {
  21. pool.add(t);
  22. sem.release();
  23. }
  24. }
  25. }
  26. // 创建对象池
  27. ObjPool<Long, String> pool =
  28. new ObjPool<Long, String>(10, 2);
  29. // 通过对象池获取t,之后执行
  30. pool.exec(t -> {
  31. System.out.println(t);
  32. return t.toString();
  33. });

最后

信号量在 Java 语言里面名气并不算大,但是在其他语言里却是很有知名度的。Java 在并发编程领域走的很快,重点支持的还是管程模型。 管程模型理论上解决了信号量模型的一些不足,主要体现在易用性和工程化方面,例如用信号量解决我们曾经提到过的阻塞队列问题,就比管程模型麻烦很多,你如果感兴趣,可以课下了解和尝试一下。