
在多线程编程中,竞态条件(race condition)是一个常见的并发问题,它指的是多个线程以不可预测的顺序访问和修改共享资源时,导致程序执行结果依赖于特定线程的执行顺序,从而产生不正确或不可预测的结果。理解竞态条件对于编写健壮的并发应用至关重要。
考虑一个常见的场景:使用多线程计算一个大数组的总和。一个初学者可能会编写出如下代码,期望它能展示竞态条件,但结果却总是正确的。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncDemo1 {
public static void main(String[] args) {
new SyncDemo1().startThread();
}
private void startThread() {
// 数组num在此处被初始化,但其元素在MyThread的run方法中并未被修改,仅用于构造器。
int[] num = new int[1000];
ExecutorService executor = Executors.newFixedThreadPool(5);
MyThread thread1 = new MyThread(num, 1, 200);
MyThread thread2 = new MyThread(num, 201, 400);
MyThread thread3 = new MyThread(num, 401, 600);
MyThread thread4 = new MyThread(num, 601, 800);
MyThread thread5 = new MyThread(num, 801, 1000);
executor.execute(thread1);
executor.execute(thread2);
executor.execute(thread3);
executor.execute(thread4);
executor.execute(thread5);
executor.shutdown();
while (!executor.isTerminated()) {
// 等待所有任务完成
}
// 各线程的局部和相加
int totalSum = thread1.getSum() + thread2.getSum() + thread3.getSum() + thread4.getSum() + thread5.getSum();
System.out.println(totalSum); // 结果总是500500
}
private static class MyThread implements Runnable {
private int[] num; // 数组num在此处作为成员变量,但其元素在run方法中并未被修改。
private int from, to, sum; // sum是每个MyThread实例的局部变量
public MyThread(int[] num, int from, int to) {
this.num = num;
this.from = from;
this.to = to;
sum = 0;
}
public void run() {
for (int i = from; i <= to; i++) {
sum += i; // 每个线程修改的是自己的局部sum变量
}
// 模拟耗时操作,但由于sum是局部变量,不会导致竞态条件
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public int getSum() {
return this.sum;
}
}
}这段代码之所以总是输出正确结果(1到1000的和为500500),是因为它并没有真正引入共享的可变状态。MyThread 类中的 sum 变量是每个 MyThread 实例私有的成员变量。每个线程在 run() 方法中累加的 sum 都是它自己独有的,不会与其他线程的 sum 变量发生冲突。虽然 int[] num 数组被所有 MyThread 实例共享,但在 run() 方法中,它并未被修改,仅仅是在构造函数中被引用。因此,这里不存在多个线程同时修改同一个共享变量的情况,自然也就不会出现竞态条件。
要真正观察到竞态条件,我们需要确保多个线程同时访问并修改一个共享的、可变的资源,并且这些修改操作不是原子性的。以下是一个演示竞态条件的典型示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class RaceConditionDemo implements Runnable {
private int counter = 0; // 共享的、可变的资源
public void increment() {
try {
// 引入短暂延迟,增加线程上下文切换的可能性,从而更容易暴露竞态条件
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
counter++; // 非原子操作:读取-修改-写入
}
public void decrement() {
counter--; // 非原子操作:读取-修改-写入
}
public int getValue() {
return counter;
}
@Override
public void run() {
this.increment();
System.out.println("线程 " + Thread.currentThread().getName() + " 增量后值: " + this.getValue());
this.decrement();
System.out.println("线程 " + Thread.currentThread().getName() + " 最终值: " + this.getValue());
}
public static void main(String args[]) {
RaceConditionDemo counterInstance = new RaceConditionDemo(); // 共享同一个实例
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executor.execute(new Thread(counterInstance, "Thread-" + (i + 1)));
}
executor.shutdown();
while (!executor.isTerminated()) {
// 等待所有任务完成
}
System.out.println("所有线程执行完毕,最终计数器值: " + counterInstance.getValue());
}
}代码分析与竞态条件表现:
立即学习“Java免费学习笔记(深入)”;
示例输出(每次运行可能不同):
线程 Thread-3 增量后值: 5 线程 Thread-5 增量后值: 5 线程 Thread-1 增量后值: 5 线程 Thread-2 增量后值: 5 线程 Thread-4 增量后值: 5 线程 Thread-2 最终值: 1 线程 Thread-1 最终值: 2 线程 Thread-5 最终值: 3 线程 Thread-3 最终值: 4 线程 Thread-4 最终值: 0 所有线程执行完毕,最终计数器值: 0
从上述输出中,我们可以观察到:
竞态条件是并发编程中的一个核心挑战。它发生在:
为了避免竞态条件,开发者需要采取适当的同步机制,确保在同一时刻只有一个线程能够访问和修改共享资源。常见的解决方案包括:
理解并能够识别竞态条件是编写正确、高效并发程序的关键一步。通过上述示例,我们不仅了解了竞态条件的表现形式,更重要的是,理解了其产生的根本原因。
以上就是Java多线程中竞态条件的原理与实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号