并发编程 Semaphore

本文将介绍并发编程中的 Semaphore 概念。

一、什么是 Semaphore?

Semaphore,信号量,它是并发编程中除管程外的另一个共享资源访问控制模型。

二、信号量模型

1. 信号量模型

信号量模型可以简单概括为:

  • 一个计数器

  • 一个等待队列

  • 三个方法(外界可访问的 API):

    • init():设置计数器的初始值

    • down():计数器值减 1,若此时计数器值 < 0,则当前线程阻塞,否则可以继续执行

      在 JUC 中,该方法对应 acquire()

    • up():计数器值加 1,若此时计数器值 <= 0,则唤醒等待队列中的一个线程

      在 JUC 中,该方法对应 release()

2. 信号量模型的理解

  • 每个希望访问资源的线程都会调用 down() 方法将计数器值减 1,并且在执行完成后才会调用 up() 方法将所减值还原,因此,被减去的值就是 正在等待线程数 + 正在执行线程数
  • 计数器值是允许同时访问的最大线程总数
  • 计数器值 - 被减去的值 表示还可以访问的线程数量
  • 执行 down() 方法,
    • 发现计数器值小于 0 时,说明同时访问线程数已超出,应该阻塞等待
    • 发现计数器值大于 0 时,说明同时访问线程数未超出,可以访问
  • 执行 up() 方法,如果发现计数器值 <= 0,说明还有线程正在阻塞等待,因此将等待队列中的线程唤醒,使其执行

3. 简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int count;

// 初始化信号量
static final Semaphore s = new Semaphore(1);

// 用信号量保证互斥
static void addOne() {
s.acquire();
try {
count++;
} finally {
s.release();
}
}

4. 限流器示例

信号量模型的特点是它可以允许多个线程访问同一个临界区。因此,我们可以利用信号量模型,构造一个 “仅允许 N 个线程同时访问” 的限流器。

下面是一个利用信号量模型实现的 “限流” 对象池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ObjPool<T, R> {

final List<T> pool;

// 信号量
final Semaphore sem;

// 获取对象并调用
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();
}
}
}

参考

  • Java 并发编程实战