多线程并发控制工具Semaphore的使用详解
目录
- 1、Semaphore类
- 2、基本概念
- 2.1、信号量
- 2.2、计数器
- 2.3、公平性
- 3、使用场景
- 4、死锁
- 4.1、条件
- 4.2、解决策略
- 4.3、联系
- 总结
当在多线程运行的场景,部分共享资源会存在资源的冲突和竞争,为了改善资源使用的方式,是否可以通过控制某个方法允许并发访问线程的数量?
如下图所示:
Semaphore可以有效的缓解这个问题。
1、Semaphore类
在jdk中提供了一个Semaphore类(信号量)
它提供了两个方法:
- semaphore.acquire() 请求信号量,可以限制线程的个数,是一个正数,如果信号量是-1,就代表已经用完了信号量,其他线程需要阻塞了。
- 第二个方法是semaphore.release(),代表是释放一个信号量,此时信号量的个数+1。
2、基本概念
2.1、信号量
Semaphore
维护了一个计数器(许可的数量),表示可以同时访问某个资源的线程数量。线程通过申请许可来访问资源。
2.2、计数器
信号量的计数器可以被设置为一个初始值,该值表示许可的数量。每当一个线程获取许可时,计数器减一;当释放许可时,计数器加一。
2.3、公平性
Semaphore
可以配置为公平或非公平。公平的信号量遵循 FIFO(先入先出)原则,非公平android信号量则不保证获取的顺序。
代码示例:
import Java.util.concurrent.Semaphore; public class SemaphoreTest { public static void main(String[] arg编程s) { final DatabaseConnectionPool pool = new DatabaseConnectionPojsol(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()http://www.devze.com;
线程尝试获取信号量的许可,如果没有可用的许可,则该线程会被阻塞,直到有许可可用。
3.释放连接
semaphore.release();
访问完成后,线程释放许可,让其他线程能够访问。
多线程模拟:
使用多个线程来模拟多个连接请求,只有 3 个线程能同时获取许可。
3、使用场景
- 控制并发访问某些资源,例如数据库连接、文件句柄等。
- 限制同时执行的线程数量,以避免系统负载过大。
4、死锁
死锁是一种情况,其中两个或多个线程永远互相等待对方释放资源,从而导致程序无法继续执行。
了解更多死锁知识,可参考:有关Java死锁和活锁的联系
4.1、条件
- 互斥条件:至少有一个资源处于非共享模式,即某一时刻只能被一个线程使用。
- 保持并等待条件:一个线程保持至少一个资源并等待其他被其他线程占用的资源。
- 不剥夺条件:资源不能被强行夺走,只能由持有该资源的线程释放。
- 循环等待条件:存在一个线程的集合,使得每个线程都在等待下一个线程持有的资源。
4.2、解决策略
- 资源顺序申请:确保所有线程按照相同的顺序请求多个资源,这样可以避免循环等待。
- 设置超时:在请求资源时设置超时,如果请求在一定时间内没有成功,线程应该释放它已持有的资源,有可能中断互斥条件。
- 使用
tryLock
和tryAcquire
:可使用Lock
和Semaphore
的尝试获取方法,在未能成功获取时,可以做适当的错误处理或重试,而不是静默等待。 - 避免持有状态:尽量避免在一个线程中持有多个锁,或者隔离资源,以减少死锁风险。
4.3、联系
虽然 Semaphore
可以在某种情况下帮助减少发生死锁的机会,但它并不是解决死锁问题的直接手段。
Semaphore
控制访问的方式可以导致某些设计上的改善,例如:
- 限制资源的同时访问:
Semaphore
可用于限制可同时访问某种资源的线程数量,从而减少复杂的资源使用模式。 - 避免持有过多的锁:通过合理设计线程的资源申请和释放逻辑,结合
Semaphore
,可以减少因线程在持有多个资源时发生互斥和等待的可能性。
总结
Semaphore 是一个非常有用的并发控制工具,可以有效地控制对共享资源的访问。通过合理使用它,可以避免过多线程同时访问相同资源造成的竞争和冲突,从而提高并发程序的安全性和效率。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.c编程客栈ppcns.com)。
精彩评论