《Java 多线程:3 个常见错误让程序崩溃(附解决方案)》
Java 多线程:3 个常见错误让程序崩溃(附解决方案)
Java 多线程编程能显著提升程序性能,但也容易引入隐蔽的错误,导致程序崩溃或行为异常。本文将分析三个最常见的错误——竞态条件、死锁和内存可见性问题,并提供实用的解决方案。每个错误都配有代码示例和修复建议,帮助您避免这些陷阱。
错误 1: 竞态条件 (Race Condition)
竞态条件发生在多个线程同时访问和修改共享数据时,导致数据不一致或程序崩溃。例如,两个线程同时增加一个计数器,可能因非原子操作而丢失更新。
错误示例代码:
public class RaceConditionExample {
private int counter = 0;
public void increment() {
counter++; // 非原子操作,可能导致问题
}
public static void main(String[] args) throws InterruptedException {
RaceConditionExample example = new RaceConditionExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final counter: " + example.counter); // 可能输出小于 2000
}
}
原因分析:counter++ 操作不是原子的,涉及读取、修改和写入。多个线程同时执行时,可能发生覆盖,导致计数器值错误。
解决方案:
使用 synchronized 关键字或 AtomicInteger 确保原子性。
// 修复方案:使用 synchronized
public synchronized void increment() {
counter++;
}
// 或使用 AtomicInteger(更高效)
import java.util.concurrent.atomic.AtomicInteger;
public class FixedRaceCondition {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
}
关键点:
- 优先使用
java.util.concurrent.atomic包中的原子类。 - 避免过度同步以减少性能开销。
错误 2: 死锁 (Deadlock)
死锁是指两个或多个线程相互等待对方释放资源,导致程序永久停滞。常见于嵌套锁或资源顺序不一致时。
错误示例代码:
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
System.out.println("Acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Acquired lock2");
}
}
}
public void method2() {
synchronized (lock2) {
System.out.println("Acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Acquired lock1");
}
}
}
public static void main(String[] args) {
DeadlockExample example = new DeadlockExample();
new Thread(example::method1).start();
new Thread(example::method2).start(); // 可能死锁
}
}
原因分析:method1 和 method2 以相反顺序获取锁(lock1 和 lock2)。当线程 A 持有 lock1 并等待 lock2,线程 B 持有 lock2 并等待 lock1 时,就会死锁。
解决方案:
统一锁获取顺序或使用 java.util.concurrent.locks.Lock 的超时机制。
// 修复方案:统一锁顺序
public void fixedMethod1() {
synchronized (lock1) {
synchronized (lock2) {
// 安全操作
}
}
}
public void fixedMethod2() {
synchronized (lock1) { // 与 method1 相同顺序
synchronized (lock2) {
// 安全操作
}
}
}
// 或使用 ReentrantLock 带超时
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FixedDeadlock {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void safeMethod() {
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 安全操作
break;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
}
关键点:
- 始终按固定顺序获取多个锁。
- 使用
tryLock()设置超时,避免无限等待。
错误 3: 内存可见性问题 (Memory Visibility Issue)
内存可见性问题是指一个线程修改共享变量后,其他线程无法立即看到变化,导致数据过时或程序崩溃。常见于未使用 volatile 或不当同步时。
错误示例代码:
public class VisibilityIssueExample {
private boolean flag = false; // 未使用 volatile,可能导致可见性问题
public void start() {
new Thread(() -> {
while (!flag) {
// 空循环,可能永远不退出
}
System.out.println("Flag changed detected");
}).start();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
flag = true; // 主线程修改 flag
System.out.println("Flag set to true");
}
public static void main(String[] args) {
new VisibilityIssueExample().start(); // 子线程可能无法检测到 flag 变化
}
}
原因分析:
由于 CPU 缓存或编译器优化,子线程可能读取到 flag 的旧值(false),导致无限循环,而主线程的修改(true)不可见。
解决方案:
使用 volatile 关键字或 synchronized 确保变量的可见性。
// 修复方案:使用 volatile
public class FixedVisibilityIssue {
private volatile boolean flag = false; // 确保修改对所有线程立即可见
public void start() {
new Thread(() -> {
while (!flag) {}
System.out.println("Flag changed detected");
}).start();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
flag = true;
System.out.println("Flag set to true");
}
}
// 或使用 synchronized 方法
public class AlternativeFix {
private boolean flag = false;
public synchronized void setFlag(boolean value) {
flag = value;
}
public synchronized boolean getFlag() {
return flag;
}
}
关键点:
volatile适用于单个变量的可见性,但不保证原子性(如复合操作)。- 对于复杂场景,使用
synchronized或java.util.concurrent工具类。
总结
多线程编程中的错误往往难以调试,但通过理解竞态条件、死锁和内存可见性问题,您可以有效预防程序崩溃:
- 竞态条件:使用原子类或同步机制保护共享数据。
- 死锁:统一锁获取顺序或使用超时锁。
- 内存可见性问题:优先使用
volatile或同步方法。 最佳实践包括:优先使用java.util.concurrent包(如ExecutorService)、避免过度同步、并进行多线程测试(如用CountDownLatch)。通过这些方案,您的 Java 多线程程序将更健壮高效。
更多推荐



所有评论(0)