
本文深入探讨了在Java中利用`Semaphore`实现线程交替执行特定方法的同步机制。我们将分析一个常见的同步问题,即如何确保两个线程严格按照1-2-1-2的顺序打印输出,并详细解释原始代码中导致同步失败的陷阱——`Semaphore`实例的错误管理。最终,我们将提供一个经过优化的解决方案,并通过代码示例和最佳实践,指导开发者正确使用`Semaphore`进行精细化的线程协作。
在多线程编程中,有时需要强制多个线程按照特定的顺序执行操作。一个典型的场景是,线程A执行完某项任务后,才允许线程B开始其任务;线程B完成后,再轮到线程A,如此循环往复。例如,我们希望两个线程分别打印数字“1”和“2”,最终输出序列为“121212...”。
实现这种精细的线程协作,Java提供了多种并发工具,其中Semaphore(信号量)是一种强大的选择,它通过管理许可数量来控制对共享资源的访问。
考虑以下使用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。
这意味着两个线程各自拥有独立的、不共享的Semaphore对象集合。线程1释放的semTest1.sem2的许可,并不能被线程2在semTest2.sem2上获取。由于semTest2.sem2初始许可为0,线程2尝试acquire()时会一直阻塞,导致整个程序无法继续交替执行。同步机制失效,线程之间无法进行有效的协作。
为了实现线程间的协作,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(); // 给线程命名方便调试
}
}代码解析:
通过这种精确的acquire()和release()交替操作,两个线程得以严格按照“121212...”的顺序协作执行。
通过本教程,我们深入探讨了如何利用Java的Semaphore实现线程间的精确交替执行。关键在于确保所有参与同步的线程都操作同一个共享的Semaphore实例。错误的Semaphore实例化(如每个线程拥有独立的Semaphore实例)是导致同步失败的常见陷阱。理解并正确应用Semaphore的共享性原则和许可机制,是编写健壮、高效并发程序的关键。
以上就是Java并发:使用Semaphore实现线程交替执行的精确同步的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号