Java 多线程基础要掌握三件事:线程处于什么状态、线程任务如何创建、线程之间如何协作。真正写业务代码时,还要判断哪些逻辑适合并发,哪些逻辑必须串行或依赖数据库事务保护。
线程生命周期
Java 线程常见状态包括:
NEW:线程对象已创建,但还没有调用start()。RUNNABLE:线程已经可以被 CPU 调度,可能正在运行,也可能等待时间片。BLOCKED:等待进入synchronized临界区。WAITING:无限期等待其他线程唤醒,例如Object.wait()、Thread.join()。TIMED_WAITING:有超时时间的等待,例如sleep()、带超时的wait()。TERMINATED:线程执行结束。
需要注意:调用 start() 后,线程不一定立即执行;调用 run() 只是普通方法调用,不会启动新线程。
创建线程的常见方式
直接继承 Thread:
1 | class InventorySyncThread extends Thread { |
实现 Runnable:
1 | class InventorySyncTask implements Runnable { |
使用 Callable 获取返回值:
1 | Callable<Integer> task = () -> inventoryService.countLowStockSku(); |
生产代码中更推荐使用线程池,而不是直接 new Thread():
1 | ExecutorService executor = Executors.newFixedThreadPool(8); |
线程池可以控制并发数量、复用线程、设置队列和拒绝策略,更适合服务端应用。
供应链业务例子
库存同步任务可能要同步多个仓库:
1 | public void syncAllWarehouses(List<String> warehouseCodes) { |
这个场景适合并发,因为不同仓库之间通常可以独立同步。并发后整体耗时接近最慢的单个仓库,而不是所有仓库耗时相加。
但库存扣减不能只靠多线程提升速度。扣减涉及同一 SKU 的可用量、锁定量和库存流水,必须通过数据库条件更新、行锁或乐观锁保证一致性:
1 | UPDATE scm_inventory |
业务代码要根据影响行数判断是否预占成功。
sleep、wait、join 的区别
sleep() 让当前线程暂停一段时间,但不会释放已经持有的对象锁。
wait() 必须在 synchronized 内调用,会释放对象锁,等待其他线程 notify() 或 notifyAll()。
join() 用于等待另一个线程执行结束。
示例:
1 | Thread syncThread = new Thread(() -> inventorySyncService.syncWarehouse("WH-BJ")); |
在业务系统里,更常用的是 CountDownLatch、CompletableFuture、线程池和消息队列,而不是直接操作 wait/notify。
常见风险
第一,线程过多。线程不是越多越快,线程数超过系统承载能力后,会导致上下文切换、内存压力、连接池耗尽。
第二,共享变量不安全。多个线程同时修改 HashMap、普通 int 或业务对象时,可能出现数据错乱。
第三,异常丢失。异步任务里的异常如果没有收集和记录,主流程可能误以为任务成功。
第四,业务一致性被破坏。订单状态、库存扣减、付款确认这类流程必须有明确事务边界。
小结
多线程基础不是背 API,而是理解线程状态、任务提交、协作方式和一致性边界。供应链系统中,仓库同步、物流轨迹拉取、报表计算适合并发;库存扣减、状态流转、财务结算必须优先保证一致性。