Java并发编程的常见问题总结
并发编程是 Java 开发中最容易出问题的领域。本文总结常见并发问题及其解决方案。
1. 线程安全问题
竞态条件(Race Condition)
多个线程同时读写共享数据,结果取决于执行时序。
public class Counter { private int count = 0; public void increment() { count++; } }
|
解决方案:
public synchronized void increment() { count++; }
private AtomicInteger count = new AtomicInteger(); public void increment() { count.incrementAndGet(); }
private LongAdder count = new LongAdder(); public void increment() { count.increment(); }
|
可见性问题
一个线程修改了变量,其他线程看不到最新值。
public class VisibilityIssue { private boolean flag = false; public void writer() { flag = true; } public void reader() { while (!flag) { } } }
|
解决方案:
private volatile boolean flag = false;
|
2. 死锁(Deadlock)
互相等待
public class Deadlock { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void methodA() { synchronized (lock1) { synchronized (lock2) { } } } public void methodB() { synchronized (lock2) { synchronized (lock1) { } } } }
|
死锁的四个必要条件
- 互斥:资源一次只能被一个线程持有
- 占有且等待:持有资源的同时等待其他资源
- 不可抢占:资源只能主动释放
- 循环等待:形成等待环路
解决方案
public void methodA() { Object first = lock1.hashCode() < lock2.hashCode() ? lock1 : lock2; Object second = lock1.hashCode() < lock2.hashCode() ? lock2 : lock1; synchronized (first) { synchronized (second) { } } }
public void safeMethod() { boolean lock1Acquired = lock1.tryLock(1, TimeUnit.SECONDS); if (lock1Acquired) { try { boolean lock2Acquired = lock2.tryLock(1, TimeUnit.SECONDS); if (lock2Acquired) { try { } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } }
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
|
死锁排查
jps -l
jstack -l <pid> > thread_dump.txt
jstack -l <pid> | grep -A 50 "Found one Java-level deadlock"
|
3. 活锁(Livelock)
线程不断响应对方的行为,但无法继续执行。
public class Livelock { private boolean active = false; public void cooperate(Livelock other) { while (!active) { if (other.isActive()) { doSomething(); } else { active = true; } } } }
|
解决方案:引入随机等待。
Thread.sleep((long)(Math.random() * 100));
|
4. 饥饿(Starvation)
某些线程长期得不到执行机会。
原因:
- 高优先级线程抢占所有 CPU
- 非公平锁导致某些线程总是失败
解决方案:
ReentrantLock fairLock = new ReentrantLock(true);
Semaphore fairSemaphore = new Semaphore(10, true);
|
5. 伪共享(False Sharing)
多个线程修改不同变量,但这些变量在同一缓存行。
public class FalseSharing { volatile long value1; volatile long value2; }
|
解决方案:
public class PaddedValue { @sun.misc.Contended volatile long value1; @sun.misc.Contended volatile long value2; }
public class ManualPadding { long p1, p2, p3, p4, p5, p6, p7; volatile long value; long p8, p9, p10, p11, p12, p13, p14; }
|
6. ThreadLocal 内存泄漏
public class MemoryLeak { private static final ThreadLocal<byte[]> buffer = new ThreadLocal<>(); public void process() { buffer.set(new byte[1024 * 1024 * 100]); } }
|
解决方案:
try { buffer.set(value); } finally { buffer.remove(); }
|
7. 并发修改异常
List<String> list = new ArrayList<>(); list.add("a"); list.add("b");
for (String s : list) { if (s.equals("a")) { list.remove(s); } }
|
解决方案:
Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("a")) { it.remove(); } }
list.removeIf(s -> s.equals("a"));
CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>();
|
8. 线程池滥用
问题1:无限创建线程
public void process() { ExecutorService executor = Executors.newFixedThreadPool(10); }
|
问题2:使用Executors创建无界队列
ExecutorService executor = Executors.newFixedThreadPool(100);
|
问题3:不关闭线程池
ExecutorService executor = Executors.newFixedThreadPool(10); executor.submit(() -> { ... });
|
正确做法:
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor( 4, 8, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadFactoryBuilder().setNameFormat("pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() );
Runtime.getRuntime().addShutdownHook(new Thread(() -> { EXECUTOR.shutdown(); try { if (!EXECUTOR.awaitTermination(60, TimeUnit.SECONDS)) { EXECUTOR.shutdownNow(); } } catch (InterruptedException e) { EXECUTOR.shutdownNow(); } }));
|
9. 并发集合的复合操作
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
if (!map.containsKey("key")) { map.put("key", "value"); }
map.putIfAbsent("key", "value");
map.computeIfAbsent("key", k -> createValue());
|
10. 总结检查清单
并发问题往往难以复现和调试,预防胜于治疗。遵循最佳实践,使用成熟的并发工具类,是避免并发 Bug 的根本之道。