首页 > 编程语言 >Java并发基础:Semaphore全面解析!

Java并发基础:Semaphore全面解析!

时间:2024-01-31 18:32:10浏览次数:19  
标签:... Java 许可 获取 并发 线程 Semaphore 资源

Java并发基础:Semaphore全面解析! - 程序员古德

内容概要

Semaphore通过控制许可数量,实现了对并发线程数的精细管理,有效避免了资源竞争和过载问题,能显著提升系统吞吐量和响应速度,同时,Semaphore还支持公平与非公平策略,具有更好的灵活性和适应性,满足了不同业务场景的需求。

核心概念

Semaphorejava.util.concurrent中非常有用的并发编程工具类,它通常被用于限制对某个资源或资源池的并发访问数量。举个实际的例子:假设一个餐厅里只有 10 张桌子,在繁忙的用餐时段,很多顾客会同时来到餐厅,但餐厅的空间有限,不能同时容纳所有顾客,这时,就需要一种机制来控制进入餐厅的顾客数量,确保餐厅不会过于拥挤。

Semaphore 就可以扮演这个控制者的角色,可以将 Semaphore 的许可数设置为 10,这代表着餐厅里最多可以有 10 组顾客同时用餐,每当有顾客进入餐厅并坐下时,Semaphore 的许可数就会减 1;每当有顾客用餐完毕离开时,Semaphore 的许可数就会加 1,如果所有桌子都坐满了,后来的顾客就需要在门外等待,直到有桌子空出来。

在这种业务场景下,使用Semaphore 就可以有效地控制餐厅的拥挤程度,保证了顾客的用餐体验。

具体来说,Semaphore 通常用于以下场景:

  1. 限制并发访问量:当一个系统或应用需要限制对某个共享资源(如数据库连接、文件、网络服务等)的并发访问量时,Semaphore 可以用来控制同时访问这些资源的线程数,通过设置 Semaphore 的许可数,可以确保不会有过多的线程同时访问资源,从而防止资源过载或争用条件导致的性能下降。
  2. 实现线程同步:除了限制并发访问量外,Semaphore 还可以用于协调多个线程的执行顺序,例如,在一个多线程程序中,如果某个操作需要多个线程按照特定的顺序执行,可以使用 Semaphore 来控制这些线程的执行流程。
  3. 资源池管理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 并不保证获取许可的公平性,即等待时间最长的线程不一定会优先获得许可。

核心总结

Java并发基础:Semaphore全面解析! - 程序员古德

优点

  1. 简单易用:通过许可数量,轻松控制并发线程数。
  2. 高效:避免了不必要的线程创建和销毁,提高了系统吞吐量。
  3. 灵活:支持公平和非公平策略,可根据需求选择。

缺点

  1. 不保证公平性:默认策略下,先到的线程不一定先获得许可。
  2. 可能导致死锁:使用不当(如在持有Semaphore时执行其他阻塞操作)时,可能会引发死锁。

关注我,每天学习互联网编程技术 - 程序员古德

END!

标签:...,Java,许可,获取,并发,线程,Semaphore,资源
From: https://blog.51cto.com/bytegood/9513466

相关文章

  • Java调用ChatGPT(基于SpringBoot和Vue)实现连续对话、流式输出和自定义baseUrl
     源码及更详细的介绍说明参见Git上的README.md文档https://github.com/asleepyfish/chatgpt本文Demo(SpringBoot和Main方法Demo均包括)的Git地址:https://github.com/asleepyfish/chatgpt-demo流式输出结合Vue前端的Demo的Git地址:https://github.com/asleepyfish/chatg......
  • 面向Java开发者的ChatGPT提示词工程(4)明确步骤、GPT自己找解决方案
    在之前的文章中,我们了解到了编写明确具体的指令关键原则的四种策略,它们分别是:使用分隔符清楚地指示输入的不同部分。要求GTP结构化输出。要求GTP检查是否满足条件。写示例时提示词要尽量少一些。接下来,我们将继续了解第二个关键原则:给GPT一定的“思考”时间。给GPT......
  • 超级详细 JAVA 对接 ChatGPT 教程,实现自己的AI对话小助手
    1    前言大家好,由于近期需要对接了ChatGPTAPI所以特地记录下来,据介绍该模型是和当前官网使用的相同的模型,如果你还没体验过ChatGPT,那么今天就教大家如何打破网络壁垒,打造一个属于自己的智能助手把。本文包括APIKey的申请以及网络代理的搭建,那么事不宜迟,我们现在开......
  • java开发的chatGPT机器人系统
      ChatGPT机器人发展趋势:  更加个性化:随着数据和技术的不断进步,ChatGPT机器人将能够更加准确地理解用户的需求和偏好,并提供更加个性化的回复和服务。  多语言支持:随着ChatGPT在各个国家和地区的普及,对多语言支持的需求也越来越高。未来的ChatGPT机器人将支持更多......
  • 关于 java如何集成chatgpt,如何开发接口,如何集成vue前端界面
    Java如何集成ChatGPT,如何开发接口,如何集成Vue前端界面随着人工智能技术的不断发展,聊天机器人已经成为了人们日常生活中不可或缺的一部分。ChatGPT是一种基于深度学习的聊天机器人技术,它可以通过学习大量的语料库来生成自然流畅的对话。本文将介绍如何使用Java语言集成ChatGPT,开......
  • Java字符串池(String Pool)深度解析
    在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心,即字符串池(StringPool)。字符串池由String类私有的维护。   我们知道,在Java中有两种创建字符串对象的方式:1......
  • 在项目中如何避免Java中的内存泄漏和解决内存泄漏问题
    内存泄漏(MemoryLeak)是指程序在动态分配内存后,由于某种原因没有释放这块内存,导致这块内存无法再被使用的现象。在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序......
  • Corretto-11源码-Java命令入口
    背景由于工作中需要开发编译器,开始阅读JavaC和JDK源码了解相关过程,并做出相关整理参考本文参考ChatGPT相关解释(很多内容都是杜撰,不可信),进行自我理解后整理发出项目https://github.com/corretto/corretto-11入口(src/java.base/share/native/libjli/java.c)入口文件为java.c......
  • 重温Java基础(二)之Java线程池最全详解
    1.引言在当今高度并发的软件开发环境中,有效地管理线程是确保程序性能和稳定性的关键因素之一。Java线程池作为一种强大的并发工具,不仅能够提高任务执行的效率,还能有效地控制系统资源的使用。本文将深入探讨Java线程池的原理、参数配置、自定义以及实际应用。通过理解这些关键概......
  • kettle Redhat7连接资源库报错No more handles [MOZILLA_FIVE_HOME=''] (java.lang.Un
    今天把kettle7.1放到redhat7上运行,发现在连接资源库的时候会报一个错误,就是标题的错误。本来是想在windows上用kettle工具创建了一些job和trans打算迁移到linux上去执行,或者到任意机器上执行,突然想到这些kettle文件的还会存在迁移的问题,因为在job和trans文件里的数据库连接信息都......