这是一个聪明的策略,但你误解了如何Sempahore
发放许可证。如果您运行代码足够多次,您实际上会看到它到达了第二步:
Acquiring lock -- 5
Acquiring lock -- 1
1
Releasing lock -- 1
Acquiring lock -- 3
Acquiring lock -- 2
2
Acquiring lock -- 4
Releasing lock -- 2
如果您继续重新运行它足够多次,您实际上会看到它成功完成。发生这种情况是因为Semaphore
发放许可证。你假设Semaphore
将尽力容纳acquire()
一旦获得足够的许可,就立即致电。如果我们仔细查看文档Semaphore.aquire(int) https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Semaphore.html#acquire-int-我们会发现情况并非如此(强调我的):
如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到......其他某个线程调用其中之一release
该信号量的方法,当前线程是下一个要分配许可的线程并且可用许可证的数量满足此要求。
换句话说Semaphore
保留一个待处理获取请求的队列,并且在每次调用时.release()
, 只检查队列的头部。特别是如果您启用公平排队(将第二个构造函数参数设置为true
)你会看到甚至第一步也没有发生,因为步骤 5(通常)是队列中的第一个,甚至是新的acquire()
可以完成的呼叫将排队在其他待处理的呼叫后面。
简而言之,这意味着您不能依赖.acquire()
正如您的代码所假设的那样,尽快返回。
通过使用.tryAcquire()
在循环中,您可以避免进行任何阻塞调用(因此会给您带来更多负载)Semaphore
)并且一旦获得必要数量的许可证tryAcquire()
调用将成功获取它们。这有效但很浪费。
想象一下餐厅的等候名单。使用.aquire()
就像把你的名字放在名单上并等待被叫到一样。它可能不是完全有效,但他们会在(合理的)相当长的时间内找到你。想象一下,如果每个人都对主人大喊“你们有桌子吗?”n
还没?”尽可能多地——那是你的tryAquire()
环形。它可能仍然有效(就像您的示例中那样),但这肯定不是正确的方法。
那么你应该做什么呢?有许多可能有用的工具java.util.concurrent https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html,哪个最好在某种程度上取决于您到底想要做什么。鉴于您有效地让每个线程启动下一个线程,我可能会使用BlockingQueue
作为同步辅助,每次将下一步推入队列。然后,每个线程都会轮询队列,如果没有轮到激活的线程,则替换该值并再次等待。
这是一个例子:
public class MultiThreading {
public static void main(String[] args) throws Exception{
// Use fair queuing to prevent an out-of-order task
// from jumping to the head of the line again
// try setting this to false - you'll see far more re-queuing calls
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1, true);
for (int i = 5; i >= 1; i--) {
Thread.sleep(100); // not necessary, just helps demonstrate the queuing behavior
new MyThread(i, queue).start();
}
queue.add(1); // work starts now
}
static class MyThread extends Thread {
int var;
BlockingQueue<Integer> queue;
public MyThread(int var, BlockingQueue<Integer> queue) {
this.var = var;
this.queue = queue;
}
@Override
public void run() {
System.out.println("Task " + var + " is now pending...");
try {
while (true) {
int task = queue.take();
if (task != var) {
System.out.println(
"Task " + var + " got task " + task + " instead - re-queuing");
queue.add(task);
} else {
break;
}
}
} catch (InterruptedException e) {
// If a thread is interrupted, re-mark the thread interrupted and terminate
Thread.currentThread().interrupt();
return;
}
System.out.println("Finished task " + var);
System.out.println("Registering task " + (var + 1) + " to run next");
queue.add(var + 1);
}
}
}
这将打印以下内容并成功终止:
Task 5 is now pending...
Task 4 is now pending...
Task 3 is now pending...
Task 2 is now pending...
Task 1 is now pending...
Task 5 got task 1 instead - re-queuing
Task 4 got task 1 instead - re-queuing
Task 3 got task 1 instead - re-queuing
Task 2 got task 1 instead - re-queuing
Finished task 1
Registering task 2 to run next
Task 5 got task 2 instead - re-queuing
Task 4 got task 2 instead - re-queuing
Task 3 got task 2 instead - re-queuing
Finished task 2
Registering task 3 to run next
Task 5 got task 3 instead - re-queuing
Task 4 got task 3 instead - re-queuing
Finished task 3
Registering task 4 to run next
Task 5 got task 4 instead - re-queuing
Finished task 4
Registering task 5 to run next
Finished task 5
Registering task 6 to run next