如何在使用ExecutorService的Java多线程应用中处理与避免竞态条件?
嘿,我来帮你搞定这个竞态条件的问题!你现在的代码里,10个线程通过线程池调用sharedResource.updateData(),如果这个方法里有读写共享变量的操作却没加任何同步控制,多个线程肯定会互相干扰,导致数据错乱——这就是你遇到的竞态条件。下面给你几个实用的解决方案,适配不同的场景:
1. 给共享方法/代码块加同步锁(最直接的方案)
如果你的updateData方法里的操作需要整体同步,最省事的就是用synchronized关键字修饰方法,它会给当前SharedResource对象自动加锁,同一时间只有一个线程能执行这个方法:
class SharedResource { private int data = 0; // 用synchronized修饰方法,保证线程安全 public synchronized void updateData() { // 这里放你原来的更新逻辑,比如data累加 data++; System.out.println("Updated data to: " + data); } }
要是只有方法里的某一段代码需要同步,也可以用同步块,建议用专门的私有锁对象,避免外部代码意外干扰:
class SharedResource { private int data = 0; // 专门的锁对象,只在内部使用 private final Object lock = new Object(); public void updateData() { // 这里可以放不需要同步的代码... synchronized(lock) { data++; System.out.println("Updated data to: " + data); } // 这里也可以放不需要同步的代码... } }
2. 用原子类处理简单数值操作(更高效的方案)
如果你的共享资源是简单的数值类型(比如计数、累加),用java.util.concurrent.atomic包下的原子类会更高效——它用硬件级别的CAS(比较并交换)机制实现线程安全,不需要阻塞线程:
import java.util.concurrent.atomic.AtomicInteger; class SharedResource { // 用AtomicInteger替代普通int,自带线程安全的操作方法 private final AtomicInteger data = new AtomicInteger(0); public void updateData() { // 原子操作,不需要手动加锁 int newData = data.incrementAndGet(); System.out.println("Updated data to: " + newData); } }
这种方案比synchronized性能更好,适合简单的数值更新场景,比如统计任务执行次数、累加计数之类的。
3. 用ReentrantLock实现灵活同步(复杂场景适配)
如果你的同步逻辑比较复杂(比如需要尝试获取锁、超时等待、多条件判断),可以用ReentrantLock,它比synchronized更灵活,支持更多高级特性:
import java.util.concurrent.locks.ReentrantLock; class SharedResource { private int data = 0; private final ReentrantLock lock = new ReentrantLock(); public void updateData() { // 手动加锁 lock.lock(); try { // 这里放需要同步的更新逻辑 data++; System.out.println("Updated data to: " + data); } finally { // 必须在finally块里释放锁,避免异常导致锁无法释放 lock.unlock(); } } }
注意:用ReentrantLock一定要记得用try-finally包裹同步代码,确保锁能被正确释放,不然会导致线程池里的线程被阻塞,甚至死锁。
额外小提示
如果你的共享资源是集合类,别自己手动加锁,直接用java.util.concurrent包下的并发集合就行,比如ConcurrentHashMap、CopyOnWriteArrayList,这些集合本身就实现了线程安全,性能也比普通集合加锁好很多。
备注:内容来源于stack exchange,提问作者Harini Ramkarthik

