【问题标题】:Java Semaphore availablePermits with ThreadPoolJava Semaphore availablePermits with ThreadPool
【发布时间】:2018-08-31 04:41:54
【问题描述】:

我正在使用教程 https://www.baeldung.com/java-semaphore 学习 Java 中的信号量。

本教程中的第一个测试(低于一个)运行良好。

@Test
public void givenLoginQueue_whenReachLimit_thenBlocked() {
    int slots = 10;
    ExecutorService executorService = Executors.newFixedThreadPool(slots);
    LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots);
    IntStream.range(0, slots)
      .forEach(user -> executorService.execute(loginQueue::tryLogin));
    executorService.shutdown();
 
    assertEquals(0, loginQueue.availableSlots());
    assertFalse(loginQueue.tryLogin());
}

如果我在上面的测试用例 sn-p 中注释 executorService.shutdown(); 行,那么测试将失败并出现以下错误。

java.lang.AssertionError: 
Expected :0
Actual   :1
<Click to see difference>
at org.testng.AssertJUnit.fail(AssertJUnit.java:59)
at org.testng.AssertJUnit.failNotEquals(AssertJUnit.java:364)
at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:80)
at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:245)
at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:252)
at LoginQueueSemaphoreTest.givenLoginQueue_whenReachLimit_thenBlocked(LoginQueueSemaphoreTest.java:24)

一旦我删除评论并启用executorService.shutdown();,测试就会正常工作。

我无法理解测试(信号量可用许可)如何依赖于外部线程池。另外,当我没有释放任何许可证时,如何使用 1 个插槽?任何简短的解释都会有所帮助。提前致谢。

【问题讨论】:

  • 这是一致的行为,还是您有时会得到不同的实际值?尝试用Thread.sleep(15_000) 替换executorService.shutdown() - 在这种情况下测试是否通过?这确实看起来很奇怪,特别是因为shutdown() 不会等待提交的任务完成,但可能只是执行该调用所需的额外时间足以确保可以生成 10 个线程并在执行assertEquals() 之前执行他们的工作(调用tryLogin())。
  • shutdown() does not wait for submitted tasks to finish 这是错误的。 shutdownNow() 不等待,docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
  • @shazin 您链接的文档字面意思是“此方法不会等待先前提交的任务完成执行。使用 awaitTermination 来执行此操作。”为shutdown()shutdown() 将启动有序关闭,即让提交的任务运行到完成,但调用本身不会阻塞。
  • @JanusVarmarken - 您的建议有效。 executorService.execute 异步运行任务。添加 Thread.sleep 解决了这个问题。如果你回答了这个问题,我会接受的:)

标签: java java-8 semaphore


【解决方案1】:

这是一致的行为,还是您有时会为 Actual 得到不同的值(当省略 executorService.shutdown() 调用时)?如果您偶尔会看到其他值(例如 2 或 3),这强烈表明这完全是由于调度/计时造成的。

尝试将executorService.shutdown() 替换为Thread.sleep(15_000)。如果您进行此更改,测试是否通过?如果是,那么这表明发生的情况是,执行shutdown() 调用所花费的额外时间刚好足以确保可以生成 10 个线程并执行它们的工作(调用 tryLogin() 以减少 @ 987654328@) 之前assertEquals() 调用被执行。请注意,根据documentationshutdown() 启动了ExecutorService 的有序关闭,因为它让执行任务运行到完成,但调用本身不会 em> 在此期间阻止。结果,执行shutdown() 所花费的时间似乎完全是偶然的,足以让其他线程终止。因此,这不是一个很好的测试,因为该行为可能因不同的机器而异。相反,测试应该调用shutdown()后跟awaitTermination()(或者使用由工作人员递减的CountDownLatch来阻塞主线程)以确保assertEquals()调用不是执行直到所有工作线程都终止。

【讨论】:

  • 一个更简单的解决方案是executorService.invokeAll(Collections.nCopies(slots, loginQueue::tryLogin));,它提交所需数量的作业,然后等待它们完成。但当然,一本教科书试图展示尽可能多的不同特征是不可行的……
猜你喜欢
  • 2021-12-25
  • 1970-01-01
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
  • 2016-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多