Java 信号量的确定性如何保证?

How deterministic are Java semaphores guaranteed to be?

Semaphore.release()(Oracle) javadoc 包括:

If any threads are trying to acquire a permit, then one is selected and given the permit that was just released.

这是一个艰难的承诺吗?这意味着如果线程 A 在 acquire() 中等待并且线程 B 这样做:

sem.release()
sem.acquire()

那么 release() 应该将控制权交给 A,而 B 将在 acquire() 中被阻止。如果只有这两个线程可以持有信号量并且 doc 语句正式为真,那么这是一个完全确定的过程:之后,A 将获得许可,B 将被阻塞。

但这不是真的,或者至少在我看来是这样。我没有在这里打扰 SSCCE,因为我真的只是在寻找确认:

竞争条件适用: 即使线程 A 正在等待许可,当它被释放时它可以立即被线程 B 重新获取,留下线程 A 仍然阻塞。

这些是“公平的”信号量,如果这有什么不同的话,我实际上是在 kotlin 工作。

在问题Slaw pointed out something else from the documentation的评论中:

When fairness is set true, the semaphore guarantees that threads invoking any of the acquire methods are selected to obtain permits in the order in which their invocation of those methods was processed (first-in-first-out; FIFO). Note that FIFO ordering necessarily applies to specific internal points of execution within these methods. So, it is possible for one thread to invoke acquire before another, but reach the ordering point after the other, and similarly upon return from the method.

这里的重点是 acquire() 是一个 可中断的 函数,有开始和结束。在其异常期间的某个时刻,调用线程在公平队列中获得一个位置,但是当它与同时访问同一函数的另一个线程相关时仍然不确定。将此点称为 X 并考虑两个线程,其中一个线程持有信号量。在某个时候另一个线程调用:

   sem.acquire()

无法保证调度程序不会在到达点 X 之前将 acquire() 内的线程靠边站。 如果所有者线程随后执行此操作(这可能例如,用作某种同步检查点或屏障控制):

   sem.release()
   sem.acquire()

即使另一个线程已经进入 acquire.

,它也可以简单地释放和获取信号量而不被另一个线程获取

在调用之间注入 Thread.sleep()yield() 可能经常有效,但这不是保证。要创建具有该保证的检查点,您需要两个 locks/semaphores 进行交换:

  • 所有者线程持有 semA
  • 客户端线程可以获取 semB,然后等待 semA
  • Owner 可以释放 semA 然后等待 semB,如果另一个线程真的在等待 semA 持有 semB,将阻塞并保证 semA 现在可以被客户端获取。
  • 客户端完成后,释放semB,然后semA
  • 当所有者从 semB 的等待中释放时,它可以获取 semA 并释放 semB

如果这些被适当地封装,这个机制是坚如磐石的。