在 Java 并发编程中,Semaphore 是一个非常有用的同步工具类,属于 java.util.concurrent 包的一部分。它可以用来控制对某一资源的访问数量,允许多个线程同时访问一定数量的资源。本文将详细介绍 Semaphore 的概念、使用技巧和示例。

Java:信号量Semaphore的使用技巧与示例_互斥锁

1. 什么是 Semaphore?

Semaphore(信号量)是一种用于限制线程并发数量的机制。它维护一个可用的许可数量,每个线程在进入临界区前必须先获得许可,离开时释放许可。信号量可以用来实现各种并发控制,如互斥锁、限流器、生产者-消费者模型等。

Semaphore 主要有两个构造方法:

  • Semaphore(int permits):创建一个具有指定许可数量的信号量。
  • Semaphore(int permits, boolean fair):指定是否为公平模式的信号量。如果为 true,则按照先来先得的顺序分配许可。

2. Semaphore 的核心方法

Semaphore 主要提供以下几个方法来管理许可:

  • acquire():获取一个许可,如果没有可用的许可,当前线程会被阻塞,直到有可用的许可。
  • acquire(int permits):获取指定数量的许可。
  • tryAcquire():尝试获取一个许可,如果成功则立即返回 true,否则返回 false
  • tryAcquire(int permits):尝试获取指定数量的许可。
  • release():释放一个许可。
  • release(int permits):释放指定数量的许可。

3. Semaphore 的使用技巧

Semaphore 可以用于解决以下几种典型的并发控制问题:

1. 控制访问数量

Semaphore 可以限制同时访问某一资源的线程数量。常见场景如:数据库连接池、限流、实现限速等。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final int THREAD_COUNT = 30;
    private static final Semaphore semaphore = new Semaphore(10);  // 最多允许 10 个线程同时访问

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

        for (int i = 0; i < THREAD_COUNT; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();  // 获取一个许可
                    System.out.println(Thread.currentThread().getName() + " is accessing...");
                    Thread.sleep(1000);  // 模拟访问资源
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();  // 释放一个许可
                }
            });
        }

        executorService.shutdown();
    }
}

在上述示例中,semaphore 限制了最多有 10 个线程可以同时访问资源。其他线程必须等待,直到有可用的许可。

2. 实现生产者-消费者模式

Semaphore 可以用来实现生产者-消费者模型,其中一个信号量控制生产者生产的产品数量,另一个信号量控制消费者的消费数量。

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;

public class ProducerConsumerExample {
    private static final int BUFFER_SIZE = 5;
    private static final Semaphore items = new Semaphore(0); // 表示当前有多少个产品可消费
    private static final Semaphore spaces = new Semaphore(BUFFER_SIZE); // 表示缓冲区有多少个空间可放入产品
    private static final Queue<Integer> buffer = new LinkedList<>();

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            try {
                int value = 0;
                while (true) {
                    spaces.acquire();  // 等待缓冲区有空位
                    synchronized (buffer) {
                        buffer.add(value);
                        System.out.println("Produced: " + value);
                    }
                    items.release();  // 增加一个可消费的产品数量
                    value++;
                    Thread.sleep(500);  // 模拟生产时间
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    items.acquire();  // 等待有产品可消费
                    int value;
                    synchronized (buffer) {
                        value = buffer.poll();
                        System.out.println("Consumed: " + value);
                    }
                    spaces.release();  // 增加一个缓冲区空间
                    Thread.sleep(1000);  // 模拟消费时间
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

在这个例子中,items 信号量表示可以消费的产品数量,而 spaces 信号量表示缓冲区中可用的空间。生产者生产一个产品后,items 许可数增加,spaces 减少;消费者消费一个产品后,items 减少,spaces 增加。

3. 实现互斥锁

Semaphore 的初始许可数为 1 时,可以用来实现互斥锁(类似于 ReentrantLocksynchronized)。

import java.util.concurrent.Semaphore;

public class MutexExample {
    private static final Semaphore mutex = new Semaphore(1);  // 创建一个信号量作为互斥锁

    public static void main(String[] args) {
        Thread t1 = new Thread(new Task(), "Thread-1");
        Thread t2 = new Thread(new Task(), "Thread-2");

        t1.start();
        t2.start();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                mutex.acquire();  // 获取许可,相当于锁定
                System.out.println(Thread.currentThread().getName() + " is in critical section.");
                Thread.sleep(2000);  // 模拟临界区操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " is leaving critical section.");
                mutex.release();  // 释放许可,相当于解锁
            }
        }
    }
}

此示例使用信号量 mutex 实现了一个简单的互斥锁效果,确保同一时间只有一个线程能进入临界区。

4. 总结

  • Semaphore 是一种非常灵活的并发控制工具,可以控制对资源的访问数量。
  • Semaphore 常用于实现限流、互斥锁、生产者-消费者模型等并发场景。
  • 使用 Semaphore 可以显式地控制线程的同步行为,但需要小心避免死锁和资源泄露问题。

通过理解 Semaphore 的用法和应用场景,可以编写出更高效和健壮的多线程 Java 程序。