网站首页 > 教程文章 正文
Java并发基础:Semaphore全面解析!
内容概要
Semaphore通过控制许可数量,实现了对并发线程数的精细管理,有效避免了资源竞争和过载问题,能显著提升系统吞吐量和响应速度,同时,Semaphore还支持公平与非公平策略,具有更好的灵活性和适应性,满足了不同业务场景的需求。
核心概念
Semaphore 是 java.util.concurrent中非常有用的并发编程工具类,它通常被用于限制对某个资源或资源池的并发访问数量。举个实际的例子:假设一个餐厅里只有 10 张桌子,在繁忙的用餐时段,很多顾客会同时来到餐厅,但餐厅的空间有限,不能同时容纳所有顾客,这时,就需要一种机制来控制进入餐厅的顾客数量,确保餐厅不会过于拥挤。
Semaphore 就可以扮演这个控制者的角色,可以将 Semaphore 的许可数设置为 10,这代表着餐厅里最多可以有 10 组顾客同时用餐,每当有顾客进入餐厅并坐下时,Semaphore 的许可数就会减 1;每当有顾客用餐完毕离开时,Semaphore 的许可数就会加 1,如果所有桌子都坐满了,后来的顾客就需要在门外等待,直到有桌子空出来。
在这种业务场景下,使用Semaphore 就可以有效地控制餐厅的拥挤程度,保证了顾客的用餐体验。
具体来说,Semaphore 通常用于以下场景:
- 限制并发访问量:当一个系统或应用需要限制对某个共享资源(如数据库连接、文件、网络服务等)的并发访问量时,Semaphore 可以用来控制同时访问这些资源的线程数,通过设置 Semaphore 的许可数,可以确保不会有过多的线程同时访问资源,从而防止资源过载或争用条件导致的性能下降。
- 实现线程同步:除了限制并发访问量外,Semaphore 还可以用于协调多个线程的执行顺序,例如,在一个多线程程序中,如果某个操作需要多个线程按照特定的顺序执行,可以使用 Semaphore 来控制这些线程的执行流程。
- 资源池管理:Semaphore 还可以用于管理资源池,例如连接池、线程池等,通过动态调整 Semaphore 的许可数,可以根据系统的负载情况动态地增加或减少资源的使用量,从而提高系统的伸缩性和资源利用率。
Semaphore 是一种灵活的同步工具,它非常适合在信号量场景中控制对资源的访问,能够在多线程环境中提供细粒度的控制,帮助开发者有效地管理系统资源,比如,用来控制对数据库连接、线程池资源或其他共享资源的并发访问,从而避免资源争用和系统过载,保证系统的稳定性和性能。
代码案例
下面是一个简单的Java代码示例,演示了如何使用Semaphore来限制对一组资源的并发访问,如下代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 创建一个Semaphore,初始许可为3,表示资源池中最多有3个资源可用
private static final Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
// 创建一个固定大小的线程池来模拟客户端请求
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交10个任务到线程池,每个任务代表一个客户端请求
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
// 线程尝试获取许可
semaphore.acquire();
System.out.println("线程" + Thread.currentThread().getName() + "获取到资源,开始处理...");
// 模拟资源处理时间
Thread.sleep((long) (Math.random() * 1000));
System.out.println("线程" + Thread.currentThread().getName() + "处理完毕,释放资源...");
// 线程释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
在上面代码中,创建了一个Semaphore实例,初始许可设置为3,这意味着最多只能有3个线程同时访问资源,然后创建了一个固定大小为5的线程池来模拟客户端请求,提交了10个任务到线程池,每个任务都尝试获取Semaphore的许可,模拟资源的访问。
当线程调用semaphore.acquire()时,它会尝试获取一个许可,如果许可可用,线程将继续执行;如果没有许可可用,线程将被阻塞,直到有许可可用。当线程完成资源访问后,它调用semaphore.release()来释放许可,这样其他等待的线程就可以获取许可并访问资源了。
如下输出结果:
线程pool-1-thread-1获取到资源,开始处理...
线程pool-1-thread-2获取到资源,开始处理...
线程pool-1-thread-3获取到资源,开始处理...
线程pool-1-thread-3处理完毕,释放资源...
线程pool-1-thread-1处理完毕,释放资源...
线程pool-1-thread-4获取到资源,开始处理...
线程pool-1-thread-2处理完毕,释放资源...
线程pool-1-thread-5获取到资源,开始处理...
线程pool-1-thread-4处理完毕,释放资源...
线程pool-1-thread-5处理完毕,释放资源...
线程pool-1-thread-3获取到资源,开始处理...
线程pool-1-thread-3处理完毕,释放资源...
从输出中可以看到,尽管有一个大小为5的线程池,但Semaphore确保了同时访问资源的线程数不超过3个,当线程处理完资源后,它会释放许可,允许其他线程获取许可并继续执行。
核心API
下面是 Semaphore 类中一些重要方法的解释:
acquire()
此方法用于获取一个许可,如果当前没有可用的许可,则当前线程会被阻塞,直到有许可可用,这是 Semaphore 最基本的使用方式,用于控制对资源的访问。
acquire(int permits)
这个方法允许一次性获取多个许可,如果当前可用许可数量少于请求的数量,则线程会被阻塞,直到有足够的许可可用。
acquireUninterruptibly()
acquireUninterruptibly()和acquireUninterruptibly(int permits)这两个方法与 acquire 类似,但区别在于它们不会响应中断,即使其他线程中断了正在等待的线程,这些线程也会继续等待,直到获得许可。
tryAcquire
tryAcquire()和tryAcquire(int permits)这两个方法尝试获取一个或多个许可,如果当前有可用的许可则立即返回 true,并且获取成功;如果没有可用许可则立即返回 false,线程不会被阻塞。
tryAcquire(long timeout, TimeUnit unit)
tryAcquire(long timeout, TimeUnit unit)和tryAcquire(int permits, long timeout, TimeUnit unit)这两个方法尝试在给定的时间内获取一个或多个许可,如果在指定的时间内获得了许可,则返回 true;否则,返回 false,如果线程在等待期间被中断,那么这两个方法都会抛出 InterruptedException。
release
release()此方法用于释放一个许可,将其返回给 Semaphore,这样其他等待的线程就可以获取它,通常在完成对资源的访问后调用此方法。
release(int permits)
release(int permits)这个方法允许一次性释放多个许可。
availablePermits
availablePermits()这个方法返回当前 Semaphore 中可用的许可数量。
hasQueuedThreads()
检查是否有任何线程正在等待获取许可。
getQueueLength()
返回正在等待获取许可的线程数。
drainPermits()
此方法返回并删除当前所有可用的许可,主要用于一些特殊的场景,比如需要在某个时刻一次性消耗所有许可。
reducePermits(int reduction)
这个方法用于减少 Semaphore 中的可用许可数量,通常用于一些动态调整资源池大小的场景,需要注意的是,这个方法不会阻塞,如果减少后的许可数小于0,那么会抛出 IllegalArgumentException。
注意:Semaphore 并不保证获取许可的公平性,即等待时间最长的线程不一定会优先获得许可。
核心总结
优点
- 简单易用:通过许可数量,轻松控制并发线程数。
- 高效:避免了不必要的线程创建和销毁,提高了系统吞吐量。
- 灵活:支持公平和非公平策略,可根据需求选择。
缺点
- 不保证公平性:默认策略下,先到的线程不一定先获得许可。
- 可能导致死锁:使用不当(如在持有Semaphore时执行其他阻塞操作)时,可能会引发死锁。
作者:程序员古德
链接:https://juejin.cn/post/7330106312448000027
猜你喜欢
- 2024-12-26 进程间通信:共享内存和信号量的统一封装机制原理与实现
- 2024-12-26 python基础篇:多线程的基本使用 python3.9多线程
- 2024-12-26 介绍C#中的并发集合,并说明其在多线程编程中的作?
- 2024-12-26 Hystrix系列之信号量、线程池 信号量 线程池
- 2024-12-26 劳斯莱斯推出独一无二的Spectre Semaphore:车头泼墨,极致奢华
- 2024-12-26 Linux信号量(1)-SYSTEM V Linux信号量做进程同步
- 2024-12-26 什么是多线程同步?请列举多种多线程同步机制并?较它们的优缺点
- 2024-12-26 2 万字长文详解 10 大多线程面试题|原力计划
- 2024-12-26 进程间通信——POSIX 有名信号量与无名信号量
- 2024-12-26 Python并发编程:使用信号量Semaphore实现资源有限的并发场景
- 最近发表
- 标签列表
-
- location.href (44)
- document.ready (36)
- git checkout -b (34)
- 跃点数 (35)
- 阿里云镜像地址 (33)
- qt qmessagebox (36)
- md5 sha1 (32)
- mybatis plus page (35)
- semaphore 使用详解 (32)
- update from 语句 (32)
- vue @scroll (38)
- 堆栈区别 (33)
- 在线子域名爆破 (32)
- 什么是容器 (33)
- sha1 md5 (33)
- navicat导出数据 (34)
- 阿里云acp考试 (33)
- 阿里云 nacos (34)
- redhat官网下载镜像 (36)
- srs服务器 (33)
- pico开发者 (33)
- https的端口号 (34)
- vscode更改主题 (35)
- 阿里云资源池 (34)
- os.path.join (33)