多线程并发控制工具Semaphore的使用详解

 更新时间:2025年05月13日 10:27:48   作者:找不到、了  
这篇文章主要介绍了多线程并发控制工具Semaphore的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

当在多线程运行的场景,部分共享资源会存在资源的冲突和竞争,为了改善资源使用的方式,是否可以通过控制某个方法允许并发访问线程的数量?

如下图所示:

Semaphore可以有效的缓解这个问题。

1、Semaphore类

在jdk中提供了一个Semaphore类(信号量)

它提供了两个方法:

  • semaphore.acquire() 请求信号量,可以限制线程的个数,是一个正数,如果信号量是-1,就代表已经用完了信号量,其他线程需要阻塞了。
  • 第二个方法是semaphore.release(),代表是释放一个信号量,此时信号量的个数+1。

2、基本概念

2.1、信号量

Semaphore 维护了一个计数器(许可的数量),表示可以同时访问某个资源的线程数量。线程通过申请许可来访问资源。

2.2、计数器

信号量的计数器可以被设置为一个初始值,该值表示许可的数量。每当一个线程获取许可时,计数器减一;当释放许可时,计数器加一。

2.3、公平性

Semaphore 可以配置为公平或非公平。公平的信号量遵循 FIFO(先入先出)原则,非公平信号量则不保证获取的顺序。

代码示例:

import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    public static void main(String[] args) {
        final DatabaseConnectionPool pool = new DatabaseConnectionPool(3);

        // 创建多个线程以模拟数据库连接
        Thread thread1 = new Thread(() -> pool.connect("Thread 1"));
        Thread thread2 = new Thread(() -> pool.connect("Thread 2"));
        Thread thread3 = new Thread(() -> pool.connect("Thread 3"));
        Thread thread4 = new Thread(() -> pool.connect("Thread 4"));
        Thread thread5 = new Thread(() -> pool.connect("Thread 5"));


        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}


class DatabaseConnectionPool{
    private final Semaphore semaphore;

    DatabaseConnectionPool(int maxConnections) {
        this.semaphore = new Semaphore(maxConnections,true);
    }

    public void connect(String threadName) {
        try {
            System.out.println(threadName + " is trying to connect.");
            // 获取许可
            semaphore.acquire();
            System.out.println(threadName + " has connected to the database.");
            // 模拟使用连接
            Thread.sleep(5000); // 模拟数据库操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放许可
            System.out.println(threadName + " is releasing the connection.");
            semaphore.release();
        }
    }
}

代码解析

1.Semaphore 的创建

public DatabaseConnectionPool(int maxConnections) {
    this.semaphore = new Semaphore(maxConnections);
}

通过指定最大连接数来初始化信号量。

2.获取连接

semaphore.acquire();

线程尝试获取信号量的许可,如果没有可用的许可,则该线程会被阻塞,直到有许可可用。

3.释放连接

semaphore.release();

访问完成后,线程释放许可,让其他线程能够访问。

多线程模拟

使用多个线程来模拟多个连接请求,只有 3 个线程能同时获取许可。

3、使用场景

  • 控制并发访问某些资源,例如数据库连接、文件句柄等。
  • 限制同时执行的线程数量,以避免系统负载过大。

4、死锁

死锁是一种情况,其中两个或多个线程永远互相等待对方释放资源,从而导致程序无法继续执行。

了解更多死锁知识,可参考:有关Java死锁和活锁的联系

4.1、条件

  1. 互斥条件:至少有一个资源处于非共享模式,即某一时刻只能被一个线程使用。
  2. 保持并等待条件:一个线程保持至少一个资源并等待其他被其他线程占用的资源。
  3. 不剥夺条件:资源不能被强行夺走,只能由持有该资源的线程释放。
  4. 循环等待条件:存在一个线程的集合,使得每个线程都在等待下一个线程持有的资源。

4.2、解决策略

  • 资源顺序申请:确保所有线程按照相同的顺序请求多个资源,这样可以避免循环等待。
  • 设置超时:在请求资源时设置超时,如果请求在一定时间内没有成功,线程应该释放它已持有的资源,有可能中断互斥条件。
  • 使用 tryLock tryAcquire:可使用 LockSemaphore 的尝试获取方法,在未能成功获取时,可以做适当的错误处理或重试,而不是静默等待。
  • 避免持有状态:尽量避免在一个线程中持有多个锁,或者隔离资源,以减少死锁风险。

4.3、联系

虽然 Semaphore 可以在某种情况下帮助减少发生死锁的机会,但它并不是解决死锁问题的直接手段。

Semaphore 控制访问的方式可以导致某些设计上的改善,例如:

  1. 限制资源的同时访问Semaphore 可用于限制可同时访问某种资源的线程数量,从而减少复杂的资源使用模式。
  2. 避免持有过多的锁:通过合理设计线程的资源申请和释放逻辑,结合 Semaphore,可以减少因线程在持有多个资源时发生互斥和等待的可能性。

总结

Semaphore 是一个非常有用的并发控制工具,可以有效地控制对共享资源的访问。通过合理使用它,可以避免过多线程同时访问相同资源造成的竞争和冲突,从而提高并发程序的安全性和效率。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringCloud Feign远程调用实现详解

    SpringCloud Feign远程调用实现详解

    Feign是Netflix公司开发的一个声明式的REST调用客户端; Ribbon负载均衡、 Hystrⅸ服务熔断是我们Spring Cloud中进行微服务开发非常基础的组件,在使用的过程中我们也发现它们一般都是同时出现的,而且配置也都非常相似
    2022-11-11
  • IDEA插件之mybatisx 插件使用教程

    IDEA插件之mybatisx 插件使用教程

    这篇文章主要介绍了mybatisx 插件使用教程,包括插件安装自动生成代码的相关知识,本文通过实例图文相结合给大家介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • SpringBoot中优化if-else语句的七种方法

    SpringBoot中优化if-else语句的七种方法

    if-else语句是控制流程的基本工具,但过度使用会使代码变得复杂且难以维护,在SpringBoot , SpringCloud项目中,优化if-else结构变得尤为重要,本文将深入探讨七种策略,旨在减少SpringBoot , SpringCloud项目中 if-else的使用,需要的朋友可以参考下
    2024-07-07
  • Spring Boot 使用观察者模式实现实时库存管理的步骤

    Spring Boot 使用观察者模式实现实时库存管理的步骤

    在现代软件开发中,实时数据处理非常关键,本文提供了一个使用SpringBoot和观察者模式开发实时库存管理系统的详细教程,步骤包括创建项目、定义实体类、实现观察者模式、集成Spring框架、创建RESTful API端点和测试应用等,这将有助于开发者构建能够即时响应库存变化的系统
    2024-09-09
  • java聊天室的实现代码

    java聊天室的实现代码

    这篇文章主要为大家详细介绍了java聊天室的实现代码,一个多客户端聊天室,支持多客户端聊天,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • 分布式Netty源码分析EventLoopGroup及介绍

    分布式Netty源码分析EventLoopGroup及介绍

    这篇文章主要介绍了分布式Netty源码分析EventLoopGroup及介绍,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • alibaba seata服务端具体实现

    alibaba seata服务端具体实现

    seata是来处理分布式服务之间互相调用的事务问题,本文重点给大家介绍alibaba-seata实现方法,文中通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • Java线程池用法实战案例分析

    Java线程池用法实战案例分析

    这篇文章主要介绍了Java线程池用法,结合具体案例形式分析了java线程池创建、使用、终止等相关操作技巧与使用注意事项,需要的朋友可以参考下
    2019-10-10
  • 如何在Java中调用python文件执行详解

    如何在Java中调用python文件执行详解

    丰富的第三方库使得python非常适合用于进行数据分析,最近在项目中就涉及到java调用python实现的算法,下面这篇文章主要给大家介绍了关于如何在Java中调用python文件执行的相关资料,需要的朋友可以参考下
    2022-05-05
  • Spring内置定时任务调度@Scheduled使用详解

    Spring内置定时任务调度@Scheduled使用详解

    这篇文章主要介绍了Spring内置定时任务调度@Scheduled使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12

最新评论