首页 > Java > java教程 > 正文

Java并发:使用Semaphore实现线程交替执行的精确同步

花韻仙語
发布: 2025-11-09 14:35:01
原创
695人浏览过

java并发:使用semaphore实现线程交替执行的精确同步

本文深入探讨了在Java中利用`Semaphore`实现线程交替执行特定方法的同步机制。我们将分析一个常见的同步问题,即如何确保两个线程严格按照1-2-1-2的顺序打印输出,并详细解释原始代码中导致同步失败的陷阱——`Semaphore`实例的错误管理。最终,我们将提供一个经过优化的解决方案,并通过代码示例和最佳实践,指导开发者正确使用`Semaphore`进行精细化的线程协作。

1. 理解线程交替执行的需求

在多线程编程中,有时需要强制多个线程按照特定的顺序执行操作。一个典型的场景是,线程A执行完某项任务后,才允许线程B开始其任务;线程B完成后,再轮到线程A,如此循环往复。例如,我们希望两个线程分别打印数字“1”和“2”,最终输出序列为“121212...”。

实现这种精细的线程协作,Java提供了多种并发工具,其中Semaphore(信号量)是一种强大的选择,它通过管理许可数量来控制对共享资源的访问。

2. 初始尝试及问题分析

考虑以下使用Semaphore尝试实现“1212...”序列的代码:

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

import java.util.concurrent.Semaphore;

public class SemTest {
    Semaphore sem1 = new Semaphore(1); // 实例变量
    Semaphore sem2 = new Semaphore(0); // 实例变量

    public static void main(String args[]) {
        final SemTest semTest1 = new SemTest(); // 第一个实例
        final SemTest semTest2 = new SemTest(); // 第二个实例

        new Thread() {
            @Override
            public void run() {
                try {
                    semTest1.numb1(); // 线程1操作semTest1的实例变量
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    semTest2.numb2(); // 线程2操作semTest2的实例变量
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }.start();
    }

    private void numb1() {
        while (true) {
            try {
                sem1.acquire(); // 获取semTest1的sem1许可
                System.out.print("1");
                sem2.release(); // 释放semTest1的sem2许可
                Thread.sleep(100); // 适当缩短休眠时间,方便观察
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private void numb2() {
        while (true) {
            try {
                sem2.acquire(); // 获取semTest2的sem2许可
                System.out.print("2");
                sem1.release(); // 释放semTest2的sem1许可
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
登录后复制

这段代码的预期是线程1打印“1”,然后通知线程2打印“2”,线程2打印“2”后,再通知线程1打印“1”,如此循环。然而,实际运行时,程序通常只打印一个“1”后就停止了。

问题根源: 核心问题在于SemTest类中的sem1和sem2是实例变量。在main方法中,我们创建了两个SemTest的实例:semTest1和semTest2。

  • 第一个线程调用semTest1.numb1(),它操作的是semTest1实例内部的sem1和sem2。
  • 第二个线程调用semTest2.numb2(),它操作的是semTest2实例内部的sem1和sem2。

这意味着两个线程各自拥有独立的、不共享的Semaphore对象集合。线程1释放的semTest1.sem2的许可,并不能被线程2在semTest2.sem2上获取。由于semTest2.sem2初始许可为0,线程2尝试acquire()时会一直阻塞,导致整个程序无法继续交替执行。同步机制失效,线程之间无法进行有效的协作。

行者AI
行者AI

行者AI绘图创作,唤醒新的灵感,创造更多可能

行者AI 100
查看详情 行者AI

3. 正确的Semaphore同步方案

为了实现线程间的协作,Semaphore对象必须是共享的。这意味着所有需要通过这些Semaphore进行协调的线程,都必须引用同一个Semaphore实例。

以下是修正后的代码示例,它通过将Semaphore实例在main方法中创建并作为局部变量传递给线程(或通过匿名内部类捕获),确保了所有线程都操作相同的Semaphore对象。

import java.util.concurrent.Semaphore;

public class CorrectedSemTest {

    public static void main(String[] args) {
        // 声明并初始化两个共享的Semaphore实例
        // sem1初始许可为1,允许第一个线程立即执行
        final Semaphore sem1 = new Semaphore(1); 
        // sem2初始许可为0,阻止第二个线程在第一个线程完成前执行
        final Semaphore sem2 = new Semaphore(0); 

        // 线程1:负责打印"1"
        new Thread(() -> {
            try {
                while (true) {
                    sem1.acquire(); // 等待获取sem1的许可
                    System.out.print("1"); // 打印"1"
                    sem2.release(); // 释放sem2的许可,允许线程2执行
                    Thread.sleep(100); // 模拟工作
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread 1 interrupted.");
            }
        }, "Thread-1").start(); // 给线程命名方便调试

        // 线程2:负责打印"2"
        new Thread(() -> {
            try {
                while (true) {
                    sem2.acquire(); // 等待获取sem2的许可
                    System.out.print("2"); // 打印"2"
                    sem1.release(); // 释放sem1的许可,允许线程1再次执行
                    Thread.sleep(100); // 模拟工作
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread 2 interrupted.");
            }
        }, "Thread-2").start(); // 给线程命名方便调试
    }
}
登录后复制

代码解析:

  1. 共享Semaphore实例: sem1和sem2被定义为main方法内的final局部变量。由于匿名内部类(Runnable的lambda表达式)可以访问final或“effectively final”的局部变量,这两个Semaphore实例被两个线程共享和操作。
  2. 初始化许可:
    • sem1 = new Semaphore(1);:sem1初始有一个许可。这意味着第一个线程(打印“1”的线程)可以立即获取许可并开始执行。
    • sem2 = new Semaphore(0);:sem2初始没有许可。这意味着第二个线程(打印“2”的线程)在尝试获取sem2的许可时会立即阻塞,直到有其他线程释放了sem2的许可。
  3. 线程1 (Thread-1) 的逻辑:
    • sem1.acquire();:获取sem1的许可。由于初始有1个许可,线程1可以顺利通过。
    • System.out.print("1");:打印“1”。
    • sem2.release();:释放sem2的一个许可。此时sem2的许可数变为1,允许等待中的线程2继续执行。
  4. 线程2 (Thread-2) 的逻辑:
    • sem2.acquire();:等待获取sem2的许可。在线程1释放许可后,sem2有了1个许可,线程2可以获取并继续。
    • System.out.print("2");:打印“2”。
    • sem1.release();:释放sem1的一个许可。此时sem1的许可数变为1,允许等待中的线程1再次执行。

通过这种精确的acquire()和release()交替操作,两个线程得以严格按照“121212...”的顺序协作执行。

4. 注意事项与最佳实践

  • 共享性原则: 任何用于线程间同步的工具(如Semaphore、Lock、Condition等)都必须是所有参与同步的线程都能访问到的同一个实例。这是多线程协作的基础。
  • 初始许可的重要性: Semaphore的初始许可数量决定了哪个线程或多少个线程可以首先进入临界区。在本例中,sem1的1个许可确保了线程1优先开始。
  • acquire()与release()的配对: 务必确保acquire()和release()操作是成对出现的,并且逻辑正确。错误的配对会导致死锁或意外的并发行为。
  • 异常处理: 在acquire()方法中,通常需要捕获InterruptedException。当线程被中断时,应根据业务逻辑决定是重新尝试、清理资源还是退出。通常的最佳实践是重新设置中断标志:Thread.currentThread().interrupt();。
  • 可读性与维护性: 对于更复杂的同步场景,可以考虑将同步逻辑封装到独立的类或方法中,以提高代码的可读性和可维护性。例如,可以创建Worker1和Worker2类,并在其构造函数中传入共享的Semaphore实例。

5. 总结

通过本教程,我们深入探讨了如何利用Java的Semaphore实现线程间的精确交替执行。关键在于确保所有参与同步的线程都操作同一个共享的Semaphore实例。错误的Semaphore实例化(如每个线程拥有独立的Semaphore实例)是导致同步失败的常见陷阱。理解并正确应用Semaphore的共享性原则和许可机制,是编写健壮、高效并发程序的关键。

以上就是Java并发:使用Semaphore实现线程交替执行的精确同步的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号