semaphore翻译为信号量,它用来做什么用呢?——看JDK文档:
Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource. For example, here is a class that uses a semaphore to control access to a pool of items:
由这段英文可知,semaphore是限制线程数量的,原因往往是资源有限。接着JDK文档举了一个例子,由semaphore来限制对象池的访问。对象池的大小是固定的,其中资源的访问需要限制。
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
对象池中的对象数量是100,所以semaphore的初始值也是100。获取对象之前,必须通过semaphore的通过。getNextAvailableItem()被synchronized修饰,这是防止并发情况下,返回同一个对象,并且标记对象的使用状态。归还对象池中对象时,需要增加semaphore的值availablePermits()。
这段代码也可以看到Pool的设计思想。
想到2016年遇到的一道面试题,假设有N个线程,依次打印0, 1, 2, N-1, N, N+1, N+2 ...
2N-1……
审题可知,这道题主要考察多线程之间的同步。多个线程之间循环同步,而且输出保持有序性,可知每次输出只能有一个线程打印,这里刚好遇到semaphore.
class Worker4 implements Runnable {
private int x;
private int co;
Semaphore semaphore;
public Worker4(int x, int co, Semaphore semaphore) {
this.x = x;
this.co = co;
this.semaphore = semaphore;
}
@Override
public void run() {
while (x < 1000) {
try {
semaphore.acquire();
if (x == ConditionTest.n + 1) {
System.out.println(Thread.currentThread().getName() + "\t" + (++ConditionTest.n));
x += co;
}
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(1, false);
System.out.println(semaphore.availablePermits());
// n 是线程数量
int n = ThreadLocalRandom.current().nextInt(10, 20);
for (int i = 0; i < n; i++) {
Thread thread = new Thread(new Worker4(i, n, semaphore));
thread.start();
}
}
2019-07-18
为什么要用两个“锁”?这个问题,我很早就觉察了。现在重看代码才明白。
内部锁是必须的,因为因为get 和 put 都不是原子操作。
信号量是为了控制线程数量的,这样只会让少量的线程去竞争内部锁。
如果没有信号量,那所有的线程都会抢占内部锁,这无疑是不合理的。
所以,信号量有限流的作用。