Semaphore 类的作用是什么?请举例说明

参考回答

Semaphore 是 Java 并发包中用于控制同时访问某个资源的线程数量的类。它提供了一个计数信号量,允许一组线程以受限的方式访问共享资源,确保资源不会被过多线程同时使用。通过 Semaphore,可以实现线程的流量控制。


详细讲解与拓展

1. Semaphore 的作用

  • 限制并发访问数量:Semaphore 是一种计数器,用来限制能够同时访问某个资源的线程数量。

  • 实现许可证模型:

    • 每个线程在访问资源前,需要从 Semaphore 中获取一个许可(通过调用 acquire())。
  • 使用完资源后,线程需要释放许可(通过调用 release())。

2. Semaphore 的构造方法

  • Semaphore(int permits):创建一个信号量,指定最多允许的许可数量。

  • Semaphore(int permits, boolean fair)

    • 如果 fair 参数为 true,信号量会以公平的方式分配许可(按照线程请求的先后顺序)。
    • 如果 fair 参数为 false(默认),可能会出现“抢占”现象。

3. Semaphore 的常用方法

  • acquire():获取一个许可,如果当前没有许可可用,则线程阻塞。
  • release():释放一个许可,将其归还给信号量。
  • tryAcquire():尝试获取一个许可,如果没有许可可用,立即返回 false
  • availablePermits():返回当前可用的许可数量。

4. 示例程序

场景:限制对打印机的并发访问

假设有 3 台打印机,但有多个线程需要使用打印机。通过 Semaphore 限制最多只有 3 个线程能够同时访问打印机。

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        // 创建一个信号量,最多允许 3 个线程同时访问
        Semaphore semaphore = new Semaphore(3);

        // 创建并启动 6 个线程
        for (int i = 1; i <= 6; i++) {
            new Thread(new PrinterTask(semaphore, "Thread-" + i)).start();
        }
    }
}

class PrinterTask implements Runnable {
    private Semaphore semaphore;
    private String threadName;

    public PrinterTask(Semaphore semaphore, String threadName) {
        this.semaphore = semaphore;
        this.threadName = threadName;
    }

    @Override
    public void run() {
        try {
            System.out.println(threadName + " is waiting for a printer...");
            semaphore.acquire(); // 获取许可,可能会阻塞
            System.out.println(threadName + " is using a printer...");
            Thread.sleep((long) (Math.random() * 3000)); // 模拟打印时间
            System.out.println(threadName + " is done printing.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release(); // 释放许可
            System.out.println(threadName + " released a printer.");
        }
    }
}

运行结果(示例)

输出顺序可能因线程调度不同而变化:

Thread-1 is waiting for a printer...
Thread-2 is waiting for a printer...
Thread-3 is waiting for a printer...
Thread-1 is using a printer...
Thread-2 is using a printer...
Thread-3 is using a printer...
Thread-4 is waiting for a printer...
Thread-5 is waiting for a printer...
Thread-6 is waiting for a printer...
Thread-1 is done printing.
Thread-1 released a printer.
Thread-4 is using a printer...
Thread-2 is done printing.
Thread-2 released a printer.
Thread-5 is using a printer...
Thread-3 is done printing.
Thread-3 released a printer.
Thread-6 is using a printer...
Thread-4 is done printing.
Thread-4 released a printer.
Thread-5 is done printing.
Thread-5 released a printer.
Thread-6 is done printing.
Thread-6 released a printer.

5. 代码解析

  1. 信号量限制并发访问数量
    • Semaphore semaphore = new Semaphore(3);:创建一个最多允许 3 个线程同时访问的信号量。
    • 超过 3 个的线程会阻塞,直到有线程释放许可。
  2. 线程之间的协作
    • 当线程调用 semaphore.acquire() 时,如果没有可用许可,则线程阻塞。
    • 当线程调用 semaphore.release() 时,归还许可,其他阻塞的线程会被唤醒。
  3. 资源分配的灵活性
    • 我们可以根据资源数量调整信号量的初始许可数。

6. 扩展:公平信号量

默认情况下,信号量是非公平的,即等待线程可能会被随机唤醒。如果需要按线程请求许可的顺序唤醒,可以使用公平信号量:

Semaphore semaphore = new Semaphore(3, true); // 创建一个公平信号量

公平信号量会按照线程的调用顺序唤醒阻塞线程,适合需要严格控制访问顺序的场景。


7. 应用场景

  1. 连接池: 控制线程获取数据库连接的数量,避免连接池耗尽。
  2. 流量控制: 限制同时访问某个服务的线程数量,例如限流。
  3. 多线程资源共享: 控制对文件、打印机等共享资源的访问。

发表评论

后才能评论