并发工具类——Semaphore
本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。
Semaphore([' seməf :(r)])的主要作用是控制线程并发的数量。我们可以将Semaphore想象成景区的一个门卫,这个门卫负责发放景区入园的许可证。
景区为了游客的入园观赏体验,决定最多允许200个有个同时在园内观赏。那么这个门卫在每天开园的时候手中都会有200张许可证,每当一个游客要入园的时候门卫会给游客发放一张许可证,当门卫手中的许可证发完之后再有游客需要入园的话就必须等待。
当游客观赏完毕之后,出园的时候需要将许可证交还到门卫手上。门卫将这些交还的许可证再发等待的游客,这些游客就能顺利入园了。
Semaphore的API简介#
Semaphore的API使用起来也比较简单,常见的API简介如下:
- Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。
- Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。
- void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。
- void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试获取,在给定的时间内没获取到资源超时
- void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。
- void release(int n):释放n个许可。
- int availablePermits():当前可用的许可数。
Semaphore的常见用法#
下面给出一个Oracle官方文档中的列子代码:
class Pool {
// 可同时访问资源的最大线程数
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
// 共享资源
protected Object[] items = new Object[MAX_AVAILABLE];
protected boolean[] used = new boolean[MAX_AVAILABLE];
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
private synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
private synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
items数组可以看成是我们的共享资源,当有线程尝试使用共享资源时,我们要求线程先获得“许可”(调用Semaphore 的acquire方法),这样线程就拥有了权限,否则就需要等待。当使用完资源后,线程需要调用Semaphore 的release方法释放许可。
Semaphore并不能替代synchronized#
有些书中提到:如果将Semaphore的许可证数量设置成1的话,就能实现synchronized的功能。其实这种说法是不对的。
下面使用Semaphore来控制对一个账户进行并发存钱和取钱的动作,如果Semaphore能实现synchronized的功能的话,账户最后的余额应该还是10000,但代码执行后的结果并不是这样。大家可以执行下面的代码看下结果。
public static final int THREAD_COUNT = 100;
public static void main(String[] args) {
BankAccount myAccount = new BankAccount("accountOfMG", 10000.00);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
int var = new Random().nextInt(100);
Thread.sleep(var);
} catch (InterruptedException e) {
e.printStackTrace();
}
double deposit = myAccount.deposit(1000.00);
System.out.println(Thread.currentThread().getName() + " balance1:" + deposit);
}
}).start();
}
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
int var = new Random().nextInt(100);
Thread.sleep(var);
} catch (InterruptedException e) {
e.printStackTrace();
}
double deposit = myAccount.withdraw(1000.00);
System.out.println(Thread.currentThread().getName() + " balance2:" + deposit);
}
}).start();
}
}
private static class BankAccount {
Semaphore semaphore = new Semaphore(1);
String accountName;
double balance;
public BankAccount(String accountName, double balance) {
this.accountName = accountName;
this.balance = balance;
}
public double deposit(double amount) {
try {
semaphore.acquire();
balance = balance + amount;
return balance;
} catch (Exception e) {
throw new RuntimeException("中断...");
} finally {
semaphore.release();
}
}
public double withdraw(double amount) {
try {
semaphore.acquire();
balance = balance - amount;
return balance;
} catch (Exception e) {
throw new RuntimeException("中断...");
} finally {
semaphore.release();
}
}
}
这里Semaphore并不能实现synchronized的功能的原因是:Semaphore并不能保证共享变量的可见性。
实现原理#
Semaphore底层原理还是基于AQS机制的。这边就不具体分析了,感兴趣的可以参考我前面关于AQS的文章。
简单总结#
- Semaphore只能用来做线程同步——控制线程的执行顺序,但是并不能保证线程安全;
- Semaphore主要用来控制线程的并发数量,通常用在限流组件中。
- Semaphore基于AQS机制实现。
作者:程序员自由之路
出处:https://www.cnblogs.com/54chensongxia/p/12871187.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?