Java 多线程的核心问题可以归纳为三类:原子性、可见性、有序性。Java 内存模型,也就是 JMM,定义了线程之间如何看见彼此的写入,以及哪些同步动作能建立 happens-before 关系。供应链系统里,库存同步、价格缓存、仓库配置刷新都离不开这些基础。
可见性问题
库存同步任务通常有一个停止标志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class InventorySyncJob implements Runnable { private boolean running = true;
public void stop() { running = false; }
@Override public void run() { while (running) { syncOnce(); } } }
|
管理线程调用 stop() 后,工作线程不一定马上看到 running = false。这就是可见性问题。
可以使用 volatile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class InventorySyncJob implements Runnable { private volatile boolean running = true;
public void stop() { running = false; }
@Override public void run() { while (running) { syncOnce(); } } }
|
volatile 保证写入对其他线程可见,并限制相关指令重排。它适合任务开关、配置引用、状态标志。
volatile 不保证复合操作原子性
下面这个计数器是错误的:
1 2 3 4 5 6 7
| public class SyncCounter { private volatile int successCount;
public void success() { successCount++; } }
|
successCount++ 包含读取、加一、写回三个步骤。多个线程同时执行会丢失更新。正确方式是使用原子类:
1 2 3 4 5 6 7
| public class SyncCounter { private final AtomicInteger successCount = new AtomicInteger();
public void success() { successCount.incrementAndGet(); } }
|
如果是高并发指标统计,可以用 LongAdder:
1 2 3 4 5 6 7 8 9 10 11
| public class SyncCounter { private final LongAdder successCount = new LongAdder();
public void success() { successCount.increment(); }
public long value() { return successCount.sum(); } }
|
synchronized 解决互斥
如果多个线程要修改同一个本地库存快照 Map,需要互斥控制:
1 2 3 4 5 6 7 8 9 10 11
| public class InventorySnapshotCache { private final Map<String, Integer> cache = new HashMap<>();
public synchronized void put(String key, Integer qty) { cache.put(key, qty); }
public synchronized Integer get(String key) { return cache.get(key); } }
|
synchronized 修饰实例方法时,锁对象是 this。同一个对象上的同步方法互斥。
更推荐使用私有锁对象,避免外部代码锁住当前实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class InventorySnapshotCache { private final Object lock = new Object(); private final Map<String, Integer> cache = new HashMap<>();
public void put(String key, Integer qty) { synchronized (lock) { cache.put(key, qty); } }
public Integer get(String key) { synchronized (lock) { return cache.get(key); } } }
|
锁的边界
供应链系统一般是多实例部署。synchronized 只能保护当前 JVM 内存,不能保护数据库里的库存余额。如果两个应用实例同时扣同一条库存,Java 本地锁没有任何作用。
库存扣减必须落到数据库条件更新:
1 2 3 4 5 6
| UPDATE scm_inventory SET available_qty = available_qty - #{qty}, locked_qty = locked_qty + #{qty} WHERE warehouse_id = #{warehouseId} AND sku_id = #{skuId} AND available_qty >= #{qty};
|
Java 锁适合保护本地缓存、内存队列、对象状态;数据库事务和行锁负责保护最终业务数据。
小结
JMM 是理解 Java 多线程的基础。volatile 解决可见性和有序性,不解决复合操作原子性;synchronized 解决单 JVM 内共享状态的互斥;数据库锁解决跨实例的业务数据一致性。供应链系统里要明确每把锁保护的对象,不能用本地锁替代数据库并发控制。