java并发编程

Java 信号量Semaphore

2025-03-19  本文已影响0人  饱饱抓住了灵感

一、信号量核心概念

Semaphore是Java并发工具类,用于控制多个线程对共享资源的访问。它通过一个整数计数器表示可用资源数量:


二、基本使用示例

场景1:限制并发线程数

假设需限制同时访问数据库的线程不超过5个:

import java.util.concurrent.Semaphore;

public class DatabaseConnectionPool {
    private final Semaphore semaphore = new Semaphore(5); // 最大并发数5

    public void executeQuery() {
        try {
            semaphore.acquire(); // 获取许可
            System.out.println(Thread.currentThread().getName() + " 正在执行查询...");
            Thread.sleep(1000); // 模拟数据库操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release(); // 释放许可
            System.out.println(Thread.currentThread().getName() + " 查询完成");
        }
    }

    public static void main(String[] args) {
        DatabaseConnectionPool pool = new DatabaseConnectionPool();
        for (int i = 0; i < 10; i++) {
            new Thread(pool::executeQuery).start();
        }
    }
}

输出示例

Thread-0 正在执行查询...
Thread-1 正在执行查询...
Thread-2 正在执行查询...
Thread-3 正在执行查询...
Thread-4 正在执行查询...
Thread-5 查询完成
Thread-5 正在执行查询...
...

场景2:生产者-消费者模型

使用信号量控制生产者和消费者的节奏:

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

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final Semaphore produceSem = new Semaphore(1); // 生产许可
    private final Semaphore consumeSem = new Semaphore(0); // 消费许可
    private static final int MAX_QUEUE_SIZE = 3;

    public void produce(int item) throws InterruptedException {
        produceSem.acquire(); // 获取生产许可
        queue.add(item);
        System.out.println("生产: " + item + ",队列大小: " + queue.size());
        if (queue.size() == MAX_QUEUE_SIZE) {
            consumeSem.release(MAX_QUEUE_SIZE); // 释放对应数量的消费许可
        } else {
            consumeSem.release(1); // 每生产一个,允许消费一个
        }
    }

    public int consume() throws InterruptedException {
        consumeSem.acquire(); // 获取消费许可
        int item = queue.poll();
        System.out.println("消费: " + item + ",队列大小: " + queue.size());
        produceSem.release(1); // 释放生产许可
        return item;
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        // 生产者线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 5; j++) {
                        pc.produce(j);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }

        // 消费者线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 5; j++) {
                        pc.consume();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

输出示例

生产: 0,队列大小: 1
消费: 0,队列大小: 0
生产: 1,队列大小: 1
消费: 1,队列大小: 0
...

三、方法详解

方法 描述
acquire() 获取许可,若无可用许可则阻塞直到释放(可响应中断)
release() 释放一个许可,唤醒一个等待线程
tryAcquire(int n, TimeUnit t) 尝试获取n个许可,超时后返回false(不阻塞)
availablePermit() 返回当前可用许可数
getQueuedPermits() 返回等待获取许可的线程数

四、高级用法

1. 带超时的获取许可

boolean acquired = semaphore.tryAcquire(2, TimeUnit.SECONDS); // 尝试获取2个许可,2秒超时
if (acquired) {
    // 成功获取
} else {
    System.out.println("获取许可失败");
}

2. 释放多个许可

semaphore.release(3); // 一次性释放3个许可

五、常见问题与解决

  1. 死锁风险
    • 原因:线程未正确释放许可(如异常未捕获导致finally缺失)。
    • 解决:始终在finally块中释放许可。
  2. 资源泄漏
    • 原因:长时间未释放许可,导致后续线程无法获取。
    • 解决:使用tryAcquire()替代acquire(),设置超时时间。
  3. 性能瓶颈
    • 原因:频繁调用acquire()/release()导致上下文切换开销大。
    • 解决:批量处理资源(如批量释放许可)。

六、适用场景

  1. 资源池控制:限制数据库连接、线程池大小等。
  2. 流水线并行处理:协调多个阶段的任务(如生产者-消费者)。
  3. 限流:控制API请求速率或文件下载并发数。
  4. 同步计数器:统计全局事件发生次数(需原子操作支持)。

七、总结

通过合理使用Semaphore,可以高效协调多线程对共享资源的访问,避免竞争条件和死锁问题。

上一篇 下一篇

猜你喜欢

热点阅读