将线程与信号量同步:在 Java 中练习并发 – LeetCode 问题“交替打印 FooBar”

并发简介
在软件开发中,并发允许多个进程或线程同时执行,通常会导致更快的执行时间和更有效地利用资源。然而,有效的并发管理对于避免竞争条件、死锁和不一致状态等问题至关重要。这就是信号量等同步机制必不可少的地方。

理解信号量
信号量是一种同步工具,用于控制并发系统中多个线程对公共资源的访问。它的作用就像一个计数器,调节有多少线程可以同时访问代码的特定部分。信号量操作是:

获取:如果信号量的计数器大于零,则减少该计数器,从而允许线程继续进行。如果为零,则线程将被阻塞,直到另一个线程释放信号量。释放:增加信号量的计数器,表明线程已完成使用资源,允许其他等待线程继续。

信号量对于管理任务之间的依赖关系或确保程序的某些部分按特定顺序运行特别有用。

问题描述

class foobar {  public void foo() {    for (int i = 0; i < n; i++) {      print("foo");    }  }  public void bar() {    for (int i = 0; i < n; i++) {      print("bar");    }  }}

我们有一个 foobar 类,它有两个方法:foo() 和 bar()。这些方法旨在分别打印“foo”和“bar”。挑战在于修改该类,以便当这些方法由两个不同的线程执行时,输出始终重复读取“foobar”n 次。无论调度程序的线程执行顺序如何,都必须实现这一点。

此问题源自 leetcode,具体来自问题 1115,“print foobar alternately”。

解决方法
为了确保正确的执行顺序(“foo”后跟“bar”),我们可以使用信号量来协调线程。以下是我们如何使用信号量修改现有的 foobar 类来实现所需的行为:

引入两个信号量:一个信号量(semfoo)将控制foo()的执行,另一个信号量(sembar)将控制bar()。初始化信号量:semfoo 使用许可(count = 1)进行初始化,允许 foo() 首先执行。 sembar 在没有许可的情况下初始化(count = 0),阻止 bar() 执行,直到 foo() 完成。修改方法:每个方法在继续之前必须获取其信号量,并在执行其任务后释放另一个信号量。

这是 foobar 类的修改版本:

猫眼课题宝 猫眼课题宝

5分钟定创新选题,3步生成高质量标书!

猫眼课题宝 85 查看详情 猫眼课题宝

class foobar {    private int n;    private semaphore semfoo = new semaphore(1); // initially, allow foo() to proceed    private semaphore sembar = new semaphore(0); // initially, prevent bar() from proceeding    public foobar(int n) {        this.n = n;    }    public void foo(runnable printfoo) throws interruptedexception {        for (int i = 0; i < n; i++) {            semfoo.acquire(); // wait until it's allowed to proceed            printfoo.run();   // this will print "foo"            sembar.release(); // allow bar() to proceed        }    }    public void bar(runnable printbar) throws interruptedexception {        for (int i = 0; i < n; i++) {            sembar.acquire(); // wait until it's allowed to proceed            printbar.run();   // this will print "bar"            semfoo.release(); // allow foo() to proceed again        }    }}

运行代码
为了演示此解决方案,我们可以创建 foobar 的实例并启动两个线程,每个线程负责其中一种方法:

public class Main {    public static void main(String[] args) {        FooBar fooBar = new FooBar(2); // Example for n = 2        Thread threadA = new Thread(() -> {            try {                fooBar.foo(() -> System.out.print("foo"));            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }        });        Thread threadB = new Thread(() -> {            try {                fooBar.bar(() -> System.out.print("bar"));            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }        });        threadA.start();        threadB.start();    }}

此设置可确保 n = 2 时控制台上的输出为“foobarfoobar”,展示了信号量在控制 java 应用程序中线程执行顺序方面的强大功能。

将线程与信号量同步:在 Java 中练习并发 - LeetCode 问题“交替打印 FooBar”

立即学习“Java免费学习笔记(深入)”;

以上就是将线程与信号量同步:在 Java 中练习并发 – LeetCode 问题“交替打印 FooBar”的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/614080.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月11日 02:59:19
下一篇 2025年11月11日 03:00:16

相关推荐

  • 位域结构体有什么实用价值 位操作与硬件寄存器访问实例

    位域结构体相比普通结构体的优势在于能更精细地控制内存使用,允许按位分配内存而非字节,从而节省空间。例如,多个1bit标志在普通结构体中各自占用一字节,而位域结构体可将它们打包至同一字节。其典型应用场景包括设备驱动开发、通信协议解析和内存管理。例如,在can总线通信中,使用位域结构体定义帧id和控制字…

    2025年12月18日 好文分享
    000
  • C++自定义内存分配器怎么实现 替代标准内存管理的设计方法

    自定义内存分配器在c++++中用于优化性能、减少碎片和调试内存问题,适用于频繁分配小块内存、嵌入式系统及stl容器。1. 需要自定义分配器的原因包括:默认new/delete不适合特定场景、减少系统调用开销、避免内存碎片、提升数据结构性能;2. 设计基本分配器需提供allocate、dealloca…

    2025年12月18日 好文分享
    000
  • 怎样实现类似智能指针的类 手写简化版智能指针教学示例

    实现一个类似智能指针的类核心在于利用raii原则绑定资源与对象生命周期,通过封装原始指针确保资源自动释放,解决内存泄漏和悬空指针等问题。1. 使用模板类包装原始指针并重载解引用与成员访问运算符;2. 在析构函数中释放资源以实现自动管理;3. 禁用拷贝构造与赋值操作确保独占所有权;4. 实现移动构造与…

    2025年12月18日 好文分享
    000
  • C++宽松内存序有什么风险 探讨memory_order_relaxed使用边界

    c++++的memory_order_relaxed允许最大程度的优化,但不保证顺序性。它仅保证原子性,可能导致数据竞争和不可预测行为。适用场景包括:1. 简单计数器,如统计事件发生次数,只要最终结果正确即可;2. 收集统计信息,对精确性要求不高;3. 与锁结合使用时,因锁已提供同步保证。风险包括:…

    2025年12月18日 好文分享
    000
  • 什么是C++的内存碎片 减少内存碎片化的策略

    内存碎片是因内存分配释放导致可用内存分散为小块而无法满足大块连续请求的现象。1. 使用内存池或对象池可减少频繁分配释放,但需预知大小数量;2. 定制化分配器如伙伴系统优化分配策略,但实现复杂;3. 尽量使用vector等连续结构提高访问效率;4. 避免频繁小块分配,一次性分配后自行管理;5. 使用智…

    2025年12月18日 好文分享
    000
  • C++范围for循环怎么用 现代C++遍历容器新语法

    范围for循环是c++++11引入的语法特性,用于简化容器或数组遍历。其基本结构为for (declaration : range),其中declaration为每次迭代取出的元素变量,range为可迭代集合;例如遍历vector时无需手动控制索引,代码更简洁;使用auto可自动推导类型,避免手动输…

    2025年12月18日 好文分享
    000
  • C++如何实现多态 C++多态的实现原理与应用场景

    c++++中多态的实现依赖于虚函数和继承。具体步骤包括:1. 在基类中使用virtual关键字声明虚函数;2. 派生类继承基类并重写虚函数,保持函数签名一致;3. 通过基类指针或引用调用虚函数,实现运行时多态;4. 编译器通过虚函数表(vtable)和虚指针(vptr)机制确定实际调用的函数;5. …

    2025年12月18日 好文分享
    000
  • C++异常与标准库算法怎么配合 STL算法中的异常传播规则

    c++++异常与标准库算法配合的关键在于理解stl算法如何处理和传播异常,并在自定义代码中正确抛出和捕获异常。1. stl算法通常不主动抛出异常,而是依赖用户提供的函数对象抛出异常,算法会尝试保持容器状态一致;2. 确保异常安全需从函数对象本身的安全性、选择提供强异常保证的算法、使用事务语义等方面入…

    2025年12月18日 好文分享
    000
  • C++标准库异常类有哪些 详解std exception及其派生类

    c++++标准库异常类体系定义在中,继承自std::exception,用于报告运行错误。1. std::exception是基类,提供虚函数what()描述异常信息,不可直接抛出。2. 异常分为逻辑错误(如invalid_argument、domain_error、length_error、out…

    2025年12月18日 好文分享
    000
  • STL中的allocator有什么作用 自定义内存分配策略实现方法

    stl中的allocator主要负责对象内存的分配与释放,其核心作用是将对象构造与内存管理分离。默认使用std::allocator,通过new和delete实现基础内存操作,但自定义allocator可提供更高效的策略,例如:1. 内存池:减少系统调用提高性能;2. 固定大小分配:减少内存碎片;3…

    2025年12月18日 好文分享
    000
  • 指针在C++协程中如何处理数组 异步操作中的内存安全问题

    在c++++协程中确保数组和异步操作的内存安全,核心在于生命周期管理、智能指针使用和同步机制。1. 使用std::shared_ptr或std::unique_ptr管理数组内存,避免裸指针跨越co_await点导致悬挂;2. 优先采用值传递或std::vector简化内存管理;3. 多线程访问时使…

    2025年12月18日 好文分享
    000
  • C++如何实现文件操作的回滚机制 事务性文件处理设计

    c++++中实现文件操作的回滚机制,其核心在于手动构建“事务性”保障,以确保数据的一致性和完整性。1. 回滚机制的本质是通过预留恢复路径(如临时文件、日志记录等),在操作失败时将文件状态还原至修改前;2. 与数据库事务的区别在于,数据库内置acid特性支持原子性、一致性、隔离性和持久性,而文件系统无…

    2025年12月18日 好文分享
    000
  • 怎样用C++开发猜数字游戏 随机数生成与循环控制练习

    要提升猜数字游戏的趣味性,1. 可以增加难度等级,例如限制猜测次数或扩大数字范围;2. 加入提示功能,如显示猜测值与目标值的差距;3. 设计积分系统,根据猜测次数给予不同奖励;4. 在代码层面使用函数模块化逻辑,提高可读性和维护性。c++++11中引入的库提供了更高级的随机数生成方式,如mt1993…

    2025年12月18日 好文分享
    000
  • C++中如何实现对象池模式 提高频繁创建销毁对象性能的方法

    c++++中提升频繁创建销毁对象性能的有效策略是使用对象池模式。1. 它通过预分配对象并循环利用,减少内存分配和释放的开销;2. 实现上通常采用容器如std::queue存储空闲对象,并提供acquire()获取对象和release()归还对象的方法;3. 结合std::shared_ptr或std…

    2025年12月18日 好文分享
    000
  • 怎样使用C++17的if constexpr 编译期条件判断与代码生成

    c++++17引入的if constexpr是现代模板元编程的重要特性。它提供了一种在编译时根据常量表达式条件选择性编译代码路径的机制,与运行时if不同,不满足条件的分支不会被编译,避免了无效代码生成和潜在错误。例如,在泛型函数中可根据类型特性(如是否为整数、浮点或字符串)执行对应逻辑,提升代码清晰…

    2025年12月18日 好文分享
    000
  • C++内存碎片如何解决 内存池技术实现原理

    内存碎片分为外部碎片和内部碎片,前者指内存中分散的小空闲区域无法满足大内存请求,后者指分配器因对齐或管理需要导致实际分配大于申请内存。频繁new/delete小对象易引发外部碎片。解决方法是使用内存池,其通过预分配固定大小内存块并统一管理,避免频繁系统调用,减少碎片并提升性能。实现步骤包括:1.预分…

    2025年12月18日 好文分享
    000
  • 什么是C++中的placement new 直接内存构造的特殊用法解析

    plac++ement new 是 c++ 中用于在已分配内存上构造对象的特殊 new 运算符。1. 它不分配新内存,而是使用已有缓冲区构造对象;2. 使用后需手动调用析构函数并释放内存;3. 常用于内存池管理、自定义分配器、序列化及嵌入式系统;4. 其底层原理是重载 operator new,直接…

    2025年12月18日 好文分享
    000
  • 怎样用C++制作单位转换器 计量单位换算公式实现

    要设计一个灵活的c++++单位转换器架构,1. 引入基准单位概念,所有单位先转换为基准单位再转为目标单位;2. 使用std::map存储单位到基准单位的转换因子;3. 定义抽象unitconverter类并派生具体类如lengthconverter、temperatureconverter;4. 使…

    2025年12月18日 好文分享
    000
  • C++模板友元函数怎么声明 类模板中的友元处理技巧

    在c++++类模板中声明友元函数需根据函数是否依赖模板参数决定方式。1. 若函数与模板参数无关,可直接声明为友元,如 friend void myfriendfunc(const myclass& obj);;2. 若函数需处理不同模板类型,则应声明为模板函数友元,如 template fr…

    2025年12月18日 好文分享
    000
  • C++中如何安全地使用realloc 类型保留与对象生命周期处理

    在c++++中应尽量避免使用realloc,优先使用std::vector等标准容器。1. realloc缺乏类型安全,需手动进行类型转换并确保类型一致;2. 对象生命周期管理复杂,内存移动可能导致原有对象失效,需手动调用构造和析构函数;3. realloc失败时返回nullptr,原有内存仍需释放…

    2025年12月18日 好文分享
    000

发表回复

登录后才能评论
关注微信