Java并发——信号量Semaphore

本文深入探讨Java中的Semaphore信号量,一种用于控制对有限资源并发访问的机制。信号量通过许可证数量限制线程访问资源,确保资源的有效利用。文章详细介绍了Semaphore的构造函数、常用方法及其实现原理,通过示例代码展示了信号量在多线程环境下的应用。

1. 信号量Semaphore

信号量维护了一组许可证,以约束访问被限制资源的线程数。

类java.util.concurrent.Semaphore实现了信号量。

这段文字转自:https://blog.youkuaiyun.com/zbc1090549839/article/details/53389602 

在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机、厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种

Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

2.信号量的使用,常用方法

(1)Semaphore(int permits)构造函数

初始化一个信号量,其中permits指定了可用许可证的数量。

(2)Semaphore(int permits, boolean fair)构造函数

初始化一个信号量,其中permits指定了可用许可证的数量,fair设置公平策略。

当公平策略设置为false,信号量不会保证线程获取许可证的顺序,也就是说,抢占是允许的。

当公平策略设置为true,信号量就能保住调用acquire()方法的任意线程能按照方法被调用处理的顺序获取许可证(先进先出,FIFO)。

(3)void acquire()

从这个信号量中获取一个许可证,否则阻塞直到有一个许可证可用或者调用线程被中断。

(4)void acquire(int permits)

从这个信号量中获取permits数量的许可证,否则阻塞直到有一个许可证可用或者调用线程被中断。

(5)int availablePermits()

返回当前可用许可证的数目。

(6)int drainPermits()

获取并返回立即可用的许可证的数量。

(7)void release()

释放一个许可证,将其放回给信号量。可用许可证的数目增加1.

(8)void release(int permits)

释放permits数量的许可证,将其放回给信号量。可用许可证的数目增加permits个.

3. 示例

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

	public static void main(String[] args)
	{
		Semaphore semaphore = new Semaphore(3, true);
		Random random = new Random();
		Runnable r = new Runnable() {
			@Override
			public void run()
			{
				try {
					semaphore.acquire();
					System.out.println(Thread.currentThread().getName()+ "开始工作!");
					work();
					System.out.println(Thread.currentThread().getName()+ "工作结束!");
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				finally 
	            {
	                semaphore.release();  //在使用完之后要释放许可证
	            }
			}
			
			void work()
			{
				int worktime = random.nextInt(1000);
	            System.out.println(Thread.currentThread().getName()+"工作时间:"+ worktime);
	            try {
					Thread.sleep(worktime);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		};
		
		ExecutorService executorService = Executors.newCachedThreadPool();
		for(int i = 0; i < 6; i++)
		{
			executorService.execute(r);
		}
		
		executorService.shutdown();

	}
}

运行结果:

pool-1-thread-1开始工作!
pool-1-thread-1工作时间:319
pool-1-thread-4开始工作!
pool-1-thread-4工作时间:503
pool-1-thread-2开始工作!
pool-1-thread-2工作时间:954
pool-1-thread-1工作结束!
pool-1-thread-3开始工作!
pool-1-thread-3工作时间:996
pool-1-thread-4工作结束!
pool-1-thread-5开始工作!
pool-1-thread-5工作时间:827
pool-1-thread-2工作结束!
pool-1-thread-6开始工作!
pool-1-thread-6工作时间:857
pool-1-thread-3工作结束!
pool-1-thread-5工作结束!
pool-1-thread-6工作结束!

刚开始,只有线程1,4,2在开始工作。只有当线程1结束,释放一个许可证之后,线程3才开始工作。线程4结束工作释放许可证之后,线程5就可以开始工作了。也就是说同一时间段只能有3个许可证可用。

### Java 中多线程编程信号量的实现方式 #### 什么是信号量信号量Semaphore)是一种用于控制多个线程对共享资源访问数量的同步工具。它可以限制同时访问某一特定资源的操作数,从而防止竞争条件的发生。Java 提供了 `java.util.concurrent.Semaphore` 类来支持信号量的功能。 #### Semaphore 的基本概念 `Semaphore` 可以维护一组许可证的概念。每当调用 `acquire()` 方法时,如果还有可用的许可,则会获取一个;如果没有剩余许可,则当前线程会被阻塞直到有新的许可释放出来。当某个线程完成操作后,可以通过调用 `release()` 方法返回一个许可给其他等待中的线程使用[^3]。 #### 创建和初始化 Signal 创建一个 `Semaphore` 对象通常需要指定初始的许可数目以及是否采用公平策略: - **参数一**:表示有多少个许可可供分配; - **参数二**(可选布尔值):决定该 semaphore 是否遵循 FIFO 公平调度原则,默认为 false 表示非公平模式。 下面展示如何声明具有固定容量的 semaphores 并设置其属性: ```java // 非公平锁, 初始计数值为3 Semaphore semaphore = new Semaphore(3); ``` 或者启用公平锁定机制: ```java // 使用公平算法, 同样设定最大并发连接数为3 Semaphore semaphoreFair = new Semaphore(3, true); ``` #### 应用场景举例——数据库连接池模拟 假设我们有一个简单的数据库连接池管理器,只允许最多三个客户端同时建立连接查询数据。此时就可以借助 semaphore 来协调这些请求。 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.concurrent.Semaphore; public class DBConnectionPool { private final int MAX_CONNECTIONS = 3; // 定义最大的连接数 private Connection[] connections; private Semaphore availableConnections; public DBConnectionPool(String url) throws SQLException { this.availableConnections = new Semaphore(MAX_CONNECTIONS); connections = new Connection[MAX_CONNECTIONS]; for (int i=0;i<MAX_CONNECTIONS;i++) { connections[i]=DriverManager.getConnection(url); } } /** * 获取数据库连接的方法. */ public Connection getConnection() throws InterruptedException{ try { availableConnections.acquire(); // 尝试获得一个许可 return getFreeConnection(); } catch (InterruptedException e){ throw e; } } private synchronized Connection getFreeConnection(){ for(Connection conn :connections ){ if(!conn.isClosed()){ return conn ; } } return null ; //理论上不会到达这里 } /** * 归还数据库连接到池子里面去. */ public void releaseConnection(Connection connection){ if(connection !=null && !connection.isClosed()) { try { connection.close(); // 关闭实际物理链接 }catch(SQLException ex){} availableConnections.release(); // 返回一个许可回semaphore } } } ``` 上述例子展示了如何利用 semaphore 控制同一时间内的活动线程数量不超过预设上限,并且保证每次只有一个线程能够成功取得空闲状态下的真实 JDBC 连接对象[^4]。 #### 总结 通过以上分析可以看出,在 Java 编程环境中运用 signal 不仅能有效解决多线程间资源共享问题,而且还能简化程序逻辑结构提高开发效率。然而需要注意的是,不当配置可能会引发死锁现象或者其他性能瓶颈等问题,因此在实际项目应用过程中应当谨慎考虑各种边界情况并做好充分测试工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值