Java Semaphore

2025年3月26日 | 阅读 9 分钟

在 Java 中,我们使用 **信号量** 来进行 线程 同步。它用于控制对共享资源的访问,该共享资源使用一个计数器变量。 Java 还提供了一个 **Semaphore 类**,其中包含构造函数和各种方法来控制对共享资源的访问。我们将在本节后面讨论它。

在继续本节之前,我们将首先了解 **什么是信号量、信号量的类型**、**它是如何工作的**,以及 **如何实现信号量**。在了解了所有这些之后,我们将继续讨论 **Java 信号量程序**。

什么是信号量?

**信号量** 用于限制想要访问共享资源的线程数量。换句话说,它是一个非负变量,在线程之间共享,称为 **计数器**。它设置了线程的限制。信号量中的一个线程等待的机制可以被其他线程通知。

  • 如果 **计数器 > 0**,则允许访问共享资源。
  • 如果 **计数器 = 0**,则拒绝访问共享资源。

简而言之,计数器跟踪它已授予共享资源的权限数量。因此,信号量授予线程共享资源的权限。

信号量的特征

信号量具有以下特征:

  • 它在线程之间提供同步。
  • 它降低了同步级别。因此,提供了一种低级同步机制。
  • 信号量不包含负值。它持有的值可能大于零或等于零。
  • 我们可以使用测试操作和中断来实现信号量,并使用文件描述符来执行它。

信号量的工作原理

信号量通过一个计数器变量来控制对共享资源的访问。计数器是一个非负值。它的值可能大于 0 或等于 0。

  • 如果 **计数器 > 0**,线程将获得访问共享资源的权限,并且计数器值将 **减 1**。
  • 否则,线程将被阻塞,直到可以获取权限。
  • 当线程的执行完成时,就不再需要该资源,线程会释放它。释放资源后,计数器值将 **增加 1**。
  • 如果有另一个线程正在等待获取资源,该线程将在此时获取一个权限。
  • 如果 **计数器 = 0**,线程将不会获得访问共享资源的权限。

让我们通过流程图来理解信号量的工作原理。

Java Semaphore

信号量类型

信号量有四种类型,如下所示:

  • 计数信号量
  • 有界信号量
  • 定时信号量
  • 二进制信号量
Java Semaphore

让我们逐一详细讨论。

计数信号量

**计数信号量** 用于解决多个进程同时想要在临界区执行的情况。因此,为了解决这个问题,我们使用计数信号量。例如,考虑以下代码片段。

让我们看看计数信号量的实现。

CountingSemaphoresExample.java

有界信号量

我们可以使用 **有界信号量** 来设置上限。它用于代替计数信号量,因为计数信号量没有上限值。上限值表示 **它可以存储多少信号**。例如,考虑以下代码片段。

让我们看看有界信号量的实现。

BoundedSemaphoresExample.java

定时信号量

**定时信号量** 允许线程运行指定的时间。在特定时间后,计时器将重置并释放所有其他权限。

让我们看看定时信号量的实现。

TimedSemaphoresExample.java

二进制信号量

**二进制信号量** 与计数信号量相同。但请记住,它只接受二进制值,即 0 或 1。与其他信号量相比,它的实现更容易。如果值为 1,则信号操作成功,否则失败。

让我们看看二进制信号量的实现。

BinarySemaphoresExample.java

Java 中的信号量

在 Java 中,它是一个 **线程同步构造**。该构造使用一个称为 **计数器** 的变量来控制对共享资源的访问。它是一种用于管理并发进程并对其进行同步的变量类型。它也用于避免 **竞态条件**。它限制了访问共享资源的线程数量。

例如,我们可以限制一个文件同时最多有 10 个连接访问。

Java Semaphore 类

Java 提供了一个 **Semaphore** 类来实现信号量机制。它属于 **java.util.concurrent** 包。它实现了 **Serializable** 接口。因此,不需要手动实现。

Semaphore 类提供了以下两个构造函数:

  • Semaphore(int permits)
  • Semaphore(int permits, boolean fair)

Semaphore(int permits)

它创建一个 Semaphore 并将允许数(可用的初始允许数)作为参数解析。它指定了可以同时共享资源的线程数。permits 的值可能为负数。在这种情况下,在授予任何获取之前必须发生释放。

语法

Semaphore(int permits, boolean fair)

它使用给定的允许数和给定的公平性设置创建一个 Semaphore。

语法

它解析两个参数:

  • **permits:** permits 的值可能为 **负数**。在这种情况下,在授予任何获取之前必须发生释放。
  • **fair:** 如果设置为 **true**,则信号量保证按请求顺序对线程进行 FIFO 处理,**false** 默认情况下,所有等待资源的线程以 **未定义** 的顺序授予权限。

Semaphore 类的常用方法

该类提供了以下方法

**acquire() 方法:** 该方法从信号量获取允许,阻塞直到有可用允许,或者线程被中断。它将可用允许数减 1。

如果当前线程没有可用允许,则该线程将被禁用线程调度。当前线程进入非活动状态,直到发生以下两件事之一:

  • 如果另一个线程调用 **release()** 方法释放资源,则当前线程将获得允许。
  • 如果另一个线程中断了当前线程。

如果当前线程被中断,它会抛出 **InterruptedException**。该方法不返回任何值。

语法

**release() 方法:** 它释放一个允许并将其返回给信号量。它将可用允许数增加 1。如果一个线程尝试获取一个允许,信号量将授予该线程获取刚刚被其他线程释放的资源。此外,该线程将被考虑进行线程调度。

语法

**availablePermits() 方法:** 该方法返回信号量中可用于授予资源的允许数。通常,它用于调试和测试目的。

语法

让我们通过一个简单的例子来理解上述方法。

将信号量用作锁

Java 允许我们将信号量用作锁。这意味着它会锁定对资源的访问。任何想要访问锁定资源的线程都必须在访问资源以获取锁之前调用 **acquire()** 方法。线程在任务完成后必须通过调用 **release()** 方法来释放锁。请记住将上限设置为 1。例如,考虑以下代码片段。

让我们看一个信号量并将其用作锁的例子。

SemaphoreAsLock.java

输出

Java Semaphore

注意:当我们执行上面的程序时,每次都会得到不同的输出。所以,你的输出可能与上面显示的输出不同。

Java 信号量示例

让我们通过一个 Java 程序来理解信号量机制。在下面的示例中,我们创建了一个带有初始允许值为 3 的 Semaphore 类构造函数。

SemaphoreExample.java

输出

Java Semaphore