ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Java多线程编程实战:从基础到高并发系统设计

Java多线程编程实战:从基础到高并发系统设计 1. 为什么每个Java开发者都需要掌握多线程记得刚工作那会儿我接手了一个简单的订单处理系统。在测试环境跑得好好的程序一上线就频繁崩溃。排查了三天才发现当并发用户超过50时系统就会因为线程阻塞而雪崩。这个惨痛教训让我明白在Java世界里不会多线程就等于不会游泳却要横渡长江。现代Java应用几乎都绕不开并发编程。从Web服务器的请求处理到分布式系统的消息消费从大数据处理到高频交易系统多线程就像空气一样无处不在。但很多开发者对线程的理解还停留在Thread和Runnable的层面这就像拿着木棍参加现代战争。2. 线程基础从玩具模型到生产级代码2.1 线程生命周期深度解析教科书上那个简单的线程状态图New/Runnable/Blocked/Waiting/Timed_Waiting/Terminated在实际开发中远不够用。比如BLOCKED状态至少包含同步阻塞synchronizedIO阻塞Socket读写锁阻塞LockSupport.park// 典型错误示例 - 以为sleep会让出锁 synchronized(lock) { Thread.sleep(1000); // 实际上锁仍然被持有 }关键认知sleep()不会释放任何锁资源而wait()会释放synchronized持有的锁2.2 创建线程的四种方式对比继承Thread类适合简单demo但违反了组合优于继承原则实现Runnable解决了单继承限制但没有返回值实现Callable可以返回结果和抛出异常配合Future使用线程池提交生产环境唯一推荐方式// 使用FutureTask的正确姿势 FutureTaskInteger task new FutureTask(() - { TimeUnit.SECONDS.sleep(2); return 42; }); new Thread(task).start(); System.out.println(task.get()); // 阻塞直到获取结果3. 线程安全从原子性到内存屏障3.1 synchronized的隐藏细节很多人以为synchronized只是简单的互斥锁其实它包含三个重要特性原子性Atomicity可见性Visibility有序性Orderingclass Counter { private int count; // 实例方法锁的是this对象 public synchronized void increment() { count; } // 静态方法锁的是Class对象 public static synchronized void reset() { // ... } }3.2 volatile的适用场景volatile最适合的状态标志模式class Worker implements Runnable { private volatile boolean running true; public void stop() { running false; } Override public void run() { while(running) { // 执行任务 } } }但volatile不能保证复合操作的原子性volatile int count 0; count; // 这不是原子操作4. JUC工具库实战指南4.1 ReentrantLock的进阶用法相比synchronizedReentrantLock提供了可中断的锁获取超时获取锁公平锁选项条件变量支持Lock lock new ReentrantLock(); Condition notEmpty lock.newCondition(); void consume() throws InterruptedException { lock.lock(); try { while(queue.isEmpty()) { notEmpty.await(); // 释放锁并等待 } // 消费元素 } finally { lock.unlock(); } }4.2 并发容器性能对比容器类型线程安全实现适用场景HashMap不安全单线程环境Hashtable全表锁已淘汰ConcurrentHashMap分段锁CAS高并发读写CopyOnWriteArrayList写时复制读多写少ArrayBlockingQueueReentrantLockCondition生产者消费者模型5. 线程池的七个核心参数理解ThreadPoolExecutor的构造参数new ThreadPoolExecutor( corePoolSize, // 常驻核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 空闲线程存活时间 unit, // 时间单位 workQueue, // 任务队列 threadFactory, // 线程创建工厂 handler // 拒绝策略 );四种拒绝策略对比AbortPolicy默认直接抛出RejectedExecutionExceptionCallerRunsPolicy用调用者线程执行任务DiscardPolicy静默丢弃任务DiscardOldestPolicy丢弃队列最老的任务6. 死锁诊断与预防实战6.1 死锁产生的四个必要条件互斥条件请求与保持不可剥夺循环等待6.2 使用jstack诊断死锁jstack pid | grep -A 10 deadlock6.3 锁顺序死锁案例// 错误写法可能产生死锁 void transfer(Account from, Account to, int amount) { synchronized(from) { synchronized(to) { // 转账操作 } } } // 正确写法统一锁顺序 private static final Object tieLock new Object(); void transfer(Account from, Account to, int amount) { int fromHash System.identityHashCode(from); int toHash System.identityHashCode(to); if(fromHash toHash) { synchronized(from) { synchronized(to) { // 转账操作 } } } else if(fromHash toHash) { synchronized(to) { synchronized(from) { // 转账操作 } } } else { synchronized(tieLock) { synchronized(from) { synchronized(to) { // 转账操作 } } } } }7. 并发编程性能优化技巧7.1 减少锁竞争的方法缩小同步代码块范围降低锁粒度如ConcurrentHashMap的分段锁使用读写锁ReentrantReadWriteLock尝试无锁编程Atomic变量类7.2 线程池调优经验CPU密集型任务线程数 CPU核数 1IO密集型任务线程数 CPU核数 * (1 平均等待时间/平均计算时间)混合型任务拆分为CPU密集和IO密集两个线程池// 获取CPU核心数 int cores Runtime.getRuntime().availableProcessors(); // 最佳线程数估算公式 int threadCount cores * (1 (平均IO时间/平均CPU时间));8. CompletableFuture异步编程传统Future的局限性无法手动完成缺乏回调机制不能链式调用CompletableFuture的典型用法CompletableFuture.supplyAsync(() - queryFromDB()) .thenApplyAsync(data - processData(data)) .thenAcceptAsync(result - saveToCache(result)) .exceptionally(ex - { log.error(处理失败, ex); return null; });组合多个Future的常用模式CompletableFutureVoid all CompletableFuture.allOf(future1, future2); all.thenRun(() - { // 所有任务完成后的处理 }); CompletableFutureObject any CompletableFuture.anyOf(future1, future2); any.thenAccept(result - { // 任意任务完成后的处理 });9. 线程本地存储ThreadLocal的陷阱9.1 内存泄漏问题ThreadLocal的经典内存泄漏场景public class UserHolder { private static ThreadLocalUser holder new ThreadLocal(); public static void set(User user) { holder.set(user); } // 忘记调用remove() }正确用法try { UserHolder.set(currentUser); // 执行业务逻辑 } finally { UserHolder.remove(); // 必须清理 }9.2 InheritableThreadLocal的局限性子线程可以继承父线程的ThreadLocal值但在线程池中会失效InheritableThreadLocalString context new InheritableThreadLocal(); context.set(parent); // 线程池复用线程时不会重新继承 executor.submit(() - { System.out.println(context.get()); // 可能为null });10. 实战设计一个高并发订单系统10.1 订单状态机设计public enum OrderState { INIT { Override public void next(Order order) { order.setState(PAID); } }, PAID { Override public void next(Order order) { if(inventoryCheck()) { order.setState(SHIPPED); } } }, // 其他状态... public abstract void next(Order order); }10.2 库存扣减方案对比方案一数据库乐观锁UPDATE inventory SET stock stock - 1 WHERE product_id ? AND stock 1方案二Redis原子操作redisTemplate.opsForValue().increment(product:1, -1);方案三分布式锁try { if(redisLock.tryLock(product_1, 10, TimeUnit.SECONDS)) { // 扣减库存 } } finally { redisLock.unlock(); }11. 多线程调试技巧11.1 线程命名规范ThreadFactory factory r - { Thread t new Thread(r); t.setName(order-processor- counter.getAndIncrement()); return t; };11.2 使用ThreadMXBean监控ThreadMXBean bean ManagementFactory.getThreadMXBean(); long[] threadIds bean.findDeadlockedThreads(); if(threadIds ! null) { ThreadInfo[] infos bean.getThreadInfo(threadIds); // 打印死锁信息 }12. Java内存模型JMM核心原理12.1 happens-before规则程序顺序规则锁规则volatile变量规则线程启动规则线程终止规则中断规则终结器规则传递性12.2 内存屏障类型LoadLoad屏障StoreStore屏障LoadStore屏障StoreLoad屏障// 伪代码展示volatile写的内存屏障 volatile boolean flag false; x 42; flag true; // 插入StoreStore StoreLoad屏障13. 常见并发问题排查案例13.1 CPU占用过高排查top -Hp找出高CPU线程jstack获取线程堆栈结合十六进制线程ID分析13.2 线程阻塞分析jstack pid | grep -A 20 BLOCKED13.3 内存泄漏定位jmap生成堆转储MAT分析对象引用链重点关注ThreadLocal和静态集合14. 并发测试工具集14.1 JMH基准测试BenchmarkMode(Mode.Throughput) OutputTimeUnit(TimeUnit.SECONDS) public class LockBenchmark { Benchmark public void testSynchronized() { synchronized(this) { // 测试代码 } } Benchmark public void testReentrantLock() { lock.lock(); try { // 测试代码 } finally { lock.unlock(); } } }14.2 并发压力测试使用CountDownLatch模拟并发JMeter分布式压测ChaosBlade注入故障15. Java并发演进路线Java 5JUC工具包诞生线程池、并发集合、原子类Java 7Fork/Join框架Java 8CompletableFuture、StampedLockJava 9响应式流APIJava 11HTTP/2客户端支持异步Java 19虚拟线程预览虚拟线程使用示例try(var executor Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i - { executor.submit(() - { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }在真实项目中我发现最容易被忽视的是线程池的队列选择。曾经有个系统使用无界队列导致OOM后来改用SynchronousQueue配合合适的拒绝策略系统稳定性大幅提升。另一个经验是对于需要传递上下文信息的场景可以考虑使用ThreadLocalMDCMapped Diagnostic Context的组合既保证线程安全又便于日志追踪。
返回列表