草庐IT

提升并发性能:Java Semaphore的实战应用与优秀实践

迷路的架构师 2023-04-12 原文

一、Semaphore简介

1.1 Semaphore的概念

Semaphore(信号量)是一种计数器,用于控制同时访问特定资源的线程数量。它维护了一个许可集,当一个线程想要访问受限资源时,需要先从Semaphore中获取一个许可。如果许可数量为零,线程将阻塞,直到其他线程释放许可。Semaphore在处理多线程同步问题时可以控制并发访问数量,确保资源不被过度使用。

1.2 Semaphore的作用与使用场景

Semaphore主要用于以下场景:

  • 限制并发访问数量:在需要限制同时访问某个资源的线程数量时,可以使用Semaphore。例如,限制数据库连接数、限制服务器可处理请求数等。
  • 实现资源池:通过Semaphore可以实现资源池,如数据库连接池、线程池等。当一个线程需要使用资源时,首先尝试从Semaphore中获取许可,如果成功则使用资源,使用完毕后释放许可。
  • 实现生产者-消费者模型:Semaphore可以用于实现生产者-消费者模型,控制生产者和消费者之间的资源占用情况,以防止过度生产或消费。

通过使用Semaphore,可以有效地控制资源的并发访问,提高系统性能和稳定性。

二、Semaphore的核心方法

Semaphore提供了一系列方法来控制并发访问和许可管理。以下是一些核心方法:

2.1 acquire()

acquire()方法用于从Semaphore中获取一个许可。如果没有可用的许可,线程将阻塞,直到有许可被释放。一旦获取许可成功,Semaphore的可用许可数量将减一。

public void acquire() throws InterruptedException

2.2 release()

release()方法用于释放一个许可。释放许可后,Semaphore的可用许可数量将增加一。如果有其他线程在等待许可,它们将被唤醒并尝试获取许可。

public void release()

2.3 tryAcquire()

tryAcquire()方法尝试从Semaphore中获取一个许可,如果没有可用许可,则立即返回false,而不会阻塞线程。这种非阻塞方式有时在特定场景下更加适用。

public boolean tryAcquire()

2.4 availablePermits()

availablePermits()方法返回Semaphore当前可用的许可数量。这个值可能会在多线程环境下变化,因此返回的结果仅供参考。

public int availablePermits()

2.5 其他方法

Semaphore还提供了一些其他方法,如acquireUninterruptibly()(获取许可时不响应中断)、tryAcquire(long timeout, TimeUnit unit)(在指定时间内尝试获取许可,如果超时则返回false)等。具体可以参考Java文档以了解更多信息。

三、Semaphore的使用场景

Semaphore可以应用于多种场景,以下是一些常见的使用场景:

3.1 限制并发访问数量

在需要限制同时访问某个资源的线程数量时,可以使用Semaphore。例如,限制数据库连接数、限制服务器可处理请求数等。通过Semaphore可以避免资源过载,提高系统性能和稳定性。

3.2 实现资源池

通过Semaphore可以实现资源池,如数据库连接池、线程池等。当一个线程需要使用资源时,首先尝试从Semaphore中获取许可,如果成功则使用资源,使用完毕后释放许可。这种方式可以有效地管理资源的使用和回收。

3.3 实现生产者-消费者模型

Semaphore可以用于实现生产者-消费者模型,控制生产者和消费者之间的资源占用情况,以防止过度生产或消费。通过设置合适的许可数量,可以平衡生产者和消费者之间的速度,避免资源浪费。

四、Semaphore的实战应用

以下是一些Semaphore的实战应用示例:

4.1 使用Semaphore限制同时访问的线程数量

假设我们有一个资源,只允许最多3个线程同时访问。我们可以使用Semaphore来限制并发访问数量。

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

public class SemaphoreExample {

public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(3);

for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
semaphore.acquire();
System.out.println("Thread " + Thread.currentThread().getName() + " acquired the permit.");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println("Thread " + Thread.currentThread().getName() + " released the permit.");
}
});
}
executor.shutdown();
}
}

4.2 实现一个简单的资源池

我们可以使用Semaphore实现一个简单的资源池,如下所示:

import java.util.concurrent.Semaphore;

public class ResourcePool<T> {
private final Semaphore semaphore;
private final T[] resources;

public ResourcePool(T[] resources) {
this.resources = resources;
this.semaphore = new Semaphore(resources.length, true);
}

public T acquire() throws InterruptedException {
semaphore.acquire();
return getResource();
}

public void release(T resource) {
if (putResource(resource)) {
semaphore.release();
}
}

private synchronized T getResource() {
for (int i = 0; i < resources.length; ++i) {
if (resources[i] != null) {
T res = resources[i];
resources[i] = null;
return res;
}
}
return null;
}

private synchronized boolean putResource(T resource) {
for (int i = 0; i < resources.length; ++i) {
if (resources[i] == null) {
resources[i] = resource;
return true;
}
}
return false;
}
}

4.3 实现生产者-消费者模型

使用Semaphore,我们可以实现一个简单的生产者-消费者模型,如下所示:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;

public class ProducerConsumerExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
Semaphore producerSemaphore = new Semaphore(10);
Semaphore consumerSemaphore = new Semaphore(0);

// 生产者
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
producerSemaphore.acquire();
synchronized (queue) {
queue.add(i);
System.out.println("Produced: " + i);
}
consumerSemaphore.release();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

// 消费者
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
consumerSemaphore.acquire();
synchronized (queue) {
int value = queue.poll();
System.out.println("Consumed:" + value);
}
producerSemaphore.release();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

以上示例展示了如何使用Semaphore实现生产者-消费者模型。生产者在生产数据时,需要获取producerSemaphore许可,消费者在消费数据时,需要获取consumerSemaphore许可。生产者和消费者通过Semaphore间接实现同步和互斥。

这些实战应用示例展示了Semaphore在实际项目中的应用。在实际开发中,根据具体需求和场景选择合适的同步工具类和方法可以有效解决多线程同步问题。

五、Semaphore的局限性及替代方案

虽然Semaphore在很多场景下都能很好地解决同步问题,但它也有一些局限性。本节将介绍Semaphore的局限性以及针对这些问题的替代方案。

5.1 Semaphore的不足之处

  • 可能导致死锁:如果一个线程持有多个Semaphore许可并在获取其他许可时阻塞,同时其他线程也在尝试获取这些许可,这就可能导致死锁。在使用Semaphore时,需要注意避免死锁问题。
  • 无法控制锁的顺序:Semaphore不能控制获取许可的线程顺序,可能导致一些线程被长时间阻塞,而其他线程持续获取许可。这种情况下,可以考虑使用其他同步工具类,如ReentrantLock和Condition。
  • 不支持读写锁:Semaphore不能区分读写操作,如果需要实现读写锁功能,可以考虑使用ReentrantReadWriteLock。

5.2 ReentrantLock和Condition作为替代方案

ReentrantLock和Condition是一种更加灵活的同步工具。ReentrantLock允许线程以先进先出(FIFO)顺序获取锁,而Condition提供了一种类似于Object.wait()和Object.notify()的机制,允许线程在指定条件下等待或唤醒。ReentrantLock和Condition可以用于替代Semaphore来解决更复杂的同步问题。

5.3 使用阻塞队列实现资源管理

阻塞队列(如ArrayBlockingQueue、LinkedBlockingQueue等)提供了一种自动阻塞的同步机制,可以用于实现生产者-消费者模型,资源池等场景。当队列为空时,消费者线程将阻塞,等待生产者放入数据;当队列满时,生产者线程将阻塞,等待消费者取出数据。阻塞队列可以作为Semaphore的替代方案,用于解决特定场景下的同步问题。

六、Semaphore在实际项目中的最佳实践

以下是一些在实际项目中使用Semaphore的最佳实践:

6.1 合理设置许可数量

设置许可数量时要考虑实际需求和系统资源,避免设置过大或过小。过大的许可数量可能导致资源竞争激烈,从而影响性能;过小的许可数量可能导致线程阻塞,导致性能下降。合理的许可数量可以兼顾并发性能和资源利用率。

6.2 明确使用场景

了解Semaphore的优缺点和适用场景,确保在适当的场景下使用。例如,使用Semaphore来限制并发访问数量、实现资源池等。避免在不适用的场景下使用Semaphore,如需实现读写锁功能时,应使用ReentrantReadWriteLock。

6.3 避免死锁

在使用Semaphore时要注意避免死锁。例如,避免在一个线程中同时持有多个许可并尝试获取其他许可。如果确实需要使用多个Semaphore,考虑使用其他同步工具,如ReentrantLock和Condition,以避免死锁问题。

6.4 优雅地处理中断

在使用Semaphore的acquire()方法时,可能会抛出InterruptedException。要优雅地处理这个异常,例如,确保在异常处理代码中释放已获取的许可。可以考虑使用acquireUninterruptibly()方法来避免响应中断。

6.5 考虑使用tryAcquire()

在某些场景下,可以考虑使用非阻塞的tryAcquire()方法,以便在无法立即获取许可时立即返回。这可以避免线程长时间阻塞,从而提高系统性能。但要注意,在使用tryAcquire()时要确保资源的正确使用和释放。

6.6 遵循代码规范

在使用Semaphore时,遵循良好的代码规范,如在finally语句块中释放许可,确保资源的正确使用和释放。良好的代码规范可以避免潜在的同步问题,提高代码的可读性和可维护性。

通过遵循这些最佳实践,可以充分发挥Semaphore的优势,提高代码质量和运行性能。在实际项目中,根据需求和场景选择合适的同步工具类和方法,遵循最佳实践,可以更好地解决多线程同步问题。

有关提升并发性能:Java Semaphore的实战应用与优秀实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  5. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  6. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  7. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐