在 Java 并发编程中,线程安全问题主要发生在多个线程同时访问共享资源且未正确同步时。以下是常见的线程安全问题及其原理和示例:
i++
)public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作:实际是 read -> modify -> write
}
}
increment()
可能导致结果小于预期(如预期 20000 实际 19876)public class VisibilityIssue {
private boolean flag = true; // 未用 volatile 修饰
public void writer() {
flag = false; // 线程A修改
}
public void reader() {
while (flag) { // 线程B可能永远看不到修改
// 死循环...
}
}
}
// 线程1
synchronized(lockA) {
synchronized(lockB) { ... } // 等待lockB
}
// 线程2
synchronized(lockB) {
synchronized(lockA) { ... } // 等待lockA
}
public class Account {
private int balance = 1000;
// 非原子转账操作
public void transfer(int amount) {
balance += amount; // 步骤1
// 此时其他线程读取可能得到错误余额
balance -= fee; // 步骤2
}
}
// 线程1
while (!tryLock(lockA)) {
release(lockB); // 总是释放资源重试
}
// 线程2
while (!tryLock(lockB)) {
release(lockA); // 对称释放
}
public class ThisEscape {
private int value;
public ThisEscape() {
new Thread(() -> {
System.out.println(this.value); // 可能读到未初始化的0
}).start();
value = 42; // 初始化
}
}
ArrayList
并发修改导致 ConcurrentModificationException
HashMap
并发 put 导致链表成环(JDK7)// 错误示例
List<String> list = new ArrayList<>();
// 多线程同时调用 list.add("item") 会破坏内部结构
if (!map.containsKey(key)) { // 检查
map.put(key, value); // 执行(可能被其他线程插入)
}
使用锁机制:
// synchronized 同步
public synchronized void safeIncrement() {
count++;
}
// ReentrantLock
private Lock lock = new ReentrantLock();
public void safeMethod() {
lock.lock();
try { /* 操作 */ }
finally { lock.unlock(); }
}
原子变量:
private AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // 线程安全的自增
线程安全容器:
Map<String, String> safeMap = new ConcurrentHashMap<>();
List<String> safeList = new CopyOnWriteArrayList<>();
volatile 关键字:
private volatile boolean flag; // 保证可见性
避免共享:
// 使用 ThreadLocal
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
不可变对象:
public final class ImmutablePoint {
private final int x;
private final int y;
// 构造后状态不可变
}
高级并发工具:
// 使用 CountDownLatch 协调线程
CountDownLatch latch = new CountDownLatch(2);
// 线程完成任务后调用 latch.countDown()
latch.await(); // 等待所有完成
问题类型 | 典型表现 | 解决方案 |
---|---|---|
竞态条件 | 数据不一致(如计数错误) | 同步锁、原子变量 |
可见性问题 | 读取到过时数据 | volatile、final、锁 |
死锁/活锁 | 线程永久阻塞 | 锁顺序、超时机制 |
集合线程不安全 | ConcurrentModificationException | ConcurrentHashMap 等 |
原子性破坏 | 中间状态暴露 | 同步代码块、原子操作 |
重要原则:优先使用
java.util.concurrent
包中的并发工具(如线程池、并发集合、原子类),而非手动实现同步逻辑。