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

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

相关文章

  • java 线程池存在的意义

    java 线程池存在的意义

    这篇文章主要介绍了java线程池存在的意义,通过多线程案例模拟锁的产生的情况展开对主题的详细介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-06-06
  • 一文带你学习Java中的线程

    一文带你学习Java中的线程

    线程是系统调度的最小单元,一个进程可以包含多个线程,线程是负责执行二进制指令的。本文将详细给大家介绍一下Java中的线程,,需要的朋友可以参考下
    2023-05-05
  • SpringBoot 使用WebSocket功能(实现步骤)

    SpringBoot 使用WebSocket功能(实现步骤)

    本文通过详细步骤介绍了SpringBoot 使用WebSocket功能,首先需要导入WebSocket坐标,编写WebSocket配置类,用于注册WebSocket的Bean,结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • idea中增强for循环提示unexpected token问题

    idea中增强for循环提示unexpected token问题

    这篇文章主要介绍了idea中增强for循环提示unexpected token问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Spring之spring-context-indexer依赖详解

    Spring之spring-context-indexer依赖详解

    这篇文章主要介绍了Spring之spring-context-indexer依赖详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Spring Shell打Jar包时常用小技巧

    Spring Shell打Jar包时常用小技巧

    这篇文章主要介绍了Spring Shell打Jar包时常用小技巧,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java基于字符界面的简易收银台

    Java基于字符界面的简易收银台

    这篇文章主要为大家详细介绍了Java基于字符界面的简易收银台,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • Spring的@PropertySource注解源码解析

    Spring的@PropertySource注解源码解析

    这篇文章主要介绍了Spring的@PropertySource注解源码解析,就以源码时序图的方式,直观的感受下@PropertySource注解在Spring源码层面的执行流程,需要的朋友可以参考下
    2023-11-11
  • java实现字符串转String数组的方法示例

    java实现字符串转String数组的方法示例

    这篇文章主要介绍了java实现字符串转String数组的方法,涉及java字符串的遍历、分割、转换等相关操作技巧,需要的朋友可以参考下
    2017-10-10
  • Java实现学生管理系统

    Java实现学生管理系统

    这篇文章主要为大家详细介绍了Java实现学生管理系统,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01

最新评论