
在多线程编程中,竞态条件(race condition)是由于多个线程并发访问和修改共享资源而导致程序执行结果不确定的现象。然而,并非所有多线程场景都会自然产生竞态条件。考虑以下一个尝试使用多线程计算数组和的java代码片段:
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() {
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);
}
private static class MyThread implements Runnable {
private int[] num; // 数组本身不是共享修改的目标
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)。原因在于:
综上所述,由于缺乏共享的可变状态被多个线程同时修改,这段代码不会引发竞态条件。
要真正演示竞态条件,我们需要构造一个场景,其中多个线程并发地修改同一个共享的可变资源,并且这些修改操作不是原子性的。原子操作是指一个操作在执行过程中不会被中断,要么全部完成,要么全部不执行。像counter++或counter--这样的简单操作,在底层实际上包含三个步骤:
当多个线程并发执行这些非原子操作时,如果线程执行的步骤发生交错,就可能导致数据丢失或不一致。
立即学习“Java免费学习笔记(深入)”;
以下代码示例通过一个共享的int类型计数器来演示竞态条件。int是基本类型,非线程安全,其自增/自减操作是非原子的。为了增加竞态条件发生的概率,我们在increment()方法中引入了Thread.sleep()来模拟耗时操作,使得线程切换更容易发生在非原子操作的中间。
import java.util.concurrent.TimeUnit;
class RaceConditionDemo implements Runnable {
private int counter = 0; // 共享的计数器
public void increment() {
try {
// 模拟耗时操作,增加线程切换的可能性
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
counter++; // 非原子操作
}
public void decrement() {
counter--; // 非原子操作
}
public int getValue() {
return counter;
}
@Override
public void run() {
this.increment();
System.out.println("Value for Thread After increment "
+ Thread.currentThread().getName() + " " + this.getValue());
this.decrement();
System.out.println("Value for Thread at last "
+ Thread.currentThread().getName() + " " + this.getValue());
}
public static void main(String args[]) {
RaceConditionDemo counter = new RaceConditionDemo(); // 多个线程共享同一个RaceConditionDemo实例
Thread t1 = new Thread(counter, "Thread-1");
Thread t2 = new Thread(counter, "Thread-2");
Thread t3 = new Thread(counter, "Thread-3");
Thread t4 = new Thread(counter, "Thread-4");
Thread t5 = new Thread(counter, "Thread-5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}在这个示例中:
运行RaceConditionDemo类多次,你会发现每次的输出顺序和最终的counter值都可能不同。以下是一个可能的运行输出示例:
Value for Thread After increment Thread-3 5 Value for Thread After increment Thread-5 5 Value for Thread After increment Thread-1 5 Value for Thread After increment Thread-2 5 Value for Thread at last Thread-2 1 Value for Thread After increment Thread-4 5 Value for Thread at last Thread-1 2 Value for Thread at last Thread-5 3 Value for Thread at last Thread-3 4 Value for Thread at last Thread-4 0
分析上述输出,我们可以观察到明显的竞态条件迹象:
这种不可预测性和数据不一致性正是竞态条件的核心特征。
竞态条件是多线程编程中一个普遍且难以调试的问题。它发生在多个线程尝试同时访问和修改共享资源,并且操作顺序无法预测时。为了避免竞态条件,确保数据的一致性和程序的正确性,我们需要采取适当的同步机制:
理解竞态条件的产生机制,并熟练运用Java提供的并发工具来防范它们,是编写健壮、高效多线程程序的关键。
以上就是深入理解Java多线程中的竞态条件与非原子操作的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号