
1. 为什么Java开发者必须掌握多线程2006年Intel发布了首款双核处理器Core 2 Duo标志着计算机正式进入多核时代。如今即使是入门级手机也配备了8核CPU而Java作为一次编写到处运行的语言如何充分利用多核优势这就是多线程技术的用武之地。我在电商系统开发中曾遇到一个典型场景用户下单后需要同时执行库存扣减、优惠券核销、积分累计、物流预占等操作。最初采用单线程顺序执行平均耗时800ms在促销活动时直接导致系统崩溃。引入多线程并行处理后响应时间降至200ms以内系统吞吐量提升4倍。这个案例让我深刻认识到多线程不是选修课而是Java开发的必修技能。2. 线程基础从进程到线程的进化2.1 进程与线程的本质区别进程是操作系统资源分配的基本单位每个进程都有独立的地址空间。而线程是CPU调度的基本单位共享进程的资源。举个例子Chrome浏览器是一个进程每个标签页是一个线程它们共享浏览器的内存空间但独立执行网页渲染。Java中通过java.lang.Thread类实现线程每个线程有自己的调用栈和程序计数器但共享堆内存。这种设计带来两个关键特性线程切换成本远低于进程不需要切换地址空间线程间通信更高效直接读写共享内存2.2 Java线程的两种创建方式2.2.1 继承Thread类class MyThread extends Thread { Override public void run() { System.out.println(线程执行: Thread.currentThread().getName()); } } public static void main(String[] args) { MyThread thread new MyThread(); thread.start(); // 注意是start()不是run() }关键点直接调用run()方法只会普通方法调用start()才会真正启动新线程2.2.2 实现Runnable接口class MyRunnable implements Runnable { Override public void run() { System.out.println(Runnable线程: Thread.currentThread().getName()); } } public static void main(String[] args) { Thread thread new Thread(new MyRunnable()); thread.start(); }实际开发中更推荐实现Runnable接口因为Java不支持多重继承继承Thread后无法再继承其他类更符合面向对象设计原则组合优于继承方便线程池管理等高级功能3. 线程生命周期与状态转换3.1 六种线程状态详解JDK源码定义public enum State { NEW, // 创建未启动 RUNNABLE, // 可运行包括正在运行和就绪 BLOCKED, // 被阻塞等待监视器锁 WAITING, // 无限期等待不设超时 TIMED_WAITING, // 限期等待带超时参数 TERMINATED; // 终止 }状态转换示意图NEW --start()-- RUNNABLE --获取CPU-- RUNNING RUNNING --yield()-- RUNNABLE RUNNING --wait()/join()-- WAITING RUNNING --sleep(n)/wait(n)-- TIMED_WAITING RUNNING --等待锁-- BLOCKED 各种状态 --执行完毕/异常-- TERMINATED3.2 关键状态转换方法对比方法作用线程释放锁恢复条件常用场景sleep(long n)当前线程否时间到期定时任务wait()当前线程是notify()/notifyAll()线程间通信join()指定线程是目标线程终止线程顺序执行yield()当前线程否立即可能避免CPU独占踩坑记录我曾误用sleep()代替wait()导致死锁因为sleep()不释放锁而wait()会释放4. 线程安全与同步机制4.1 原子性问题实例public class Counter { private int count 0; public void add() { count; // 非原子操作 } }count实际包含三个步骤读取count值值1写回count多线程环境下可能出现线程A读取count0线程B读取count0线程A写入1线程B写入1 最终结果错误应为24.2 同步解决方案对比4.2.1 synchronized关键字// 同步方法 public synchronized void add() { count; } // 同步代码块 public void add() { synchronized(this) { count; } }特点悲观锁性能开销较大自动获取/释放锁可重入同一线程可重复获取同一把锁4.2.2 ReentrantLockprivate Lock lock new ReentrantLock(); public void add() { lock.lock(); try { count; } finally { lock.unlock(); // 必须手动释放 } }优势可尝试获取锁tryLock可设置公平/非公平锁支持多个条件变量4.2.3 Atomic原子类private AtomicInteger count new AtomicInteger(0); public void add() { count.incrementAndGet(); // CAS实现 }性能最高适合简单原子操作4.3 死锁案例与排查典型死锁条件四个必要条件互斥条件请求与保持不剥夺条件循环等待示例代码// 线程A synchronized(resource1) { Thread.sleep(100); synchronized(resource2) {...} } // 线程B synchronized(resource2) { Thread.sleep(100); synchronized(resource1) {...} }排查工具jstack生成线程转储查找deadlock关键词分析互相等待的线程链5. 线程池实战精要5.1 为什么不能用Executors阿里巴巴Java规范明确禁止使用Executors创建线程池因为FixedThreadPool和SingleThreadPool使用无界队列可能OOMCachedThreadPool最大线程数为Integer.MAX_VALUE可能创建过多线程正确做法是手动创建ThreadPoolExecutorExecutorService pool new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new ArrayBlockingQueue(100), // 工作队列 new ThreadFactoryBuilder().setNameFormat(order-pool-%d).build(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 );5.2 关键参数调优经验CPU密集型核心线程数 CPU核数 1IO密集型核心线程数 CPU核数 * 2混合型拆分线程池如CPU密集型任务和IO密集型任务分开监控指标活跃线程数pool.getActiveCount()任务队列大小pool.getQueue().size()历史最大线程数pool.getLargestPoolSize()5.3 四种拒绝策略对比策略行为适用场景AbortPolicy直接抛出RejectedExecutionException严格要求一致性的系统CallerRunsPolicy回退到调用者线程执行不允许丢弃任务的场景DiscardPolicy静默丢弃任务可容忍丢失的统计类任务DiscardOldestPolicy丢弃队列中最老的任务最新任务优先级更高的场景6. 并发工具类应用场景6.1 CountDownLatch vs CyclicBarrierCountDownLatch倒计时门闩一次性使用主线程等待多个工作线程完成CountDownLatch latch new CountDownLatch(3); // 工作线程 latch.countDown(); // 主线程 latch.await();CyclicBarrier循环栅栏可重复使用多个线程互相等待CyclicBarrier barrier new CyclicBarrier(3, () - { System.out.println(所有线程到达屏障); }); // 工作线程 barrier.await();6.2 ThreadLocal内存泄漏防范典型用法private static ThreadLocalSimpleDateFormat dateFormat ThreadLocal.withInitial(() - new SimpleDateFormat(yyyy-MM-dd)); public static String format(Date date) { return dateFormat.get().format(date); }内存泄漏风险线程池中的线程会复用ThreadLocalMap的Entry是弱引用Key但Value是强引用必须手动remove()正确模式try { // 使用ThreadLocal } finally { dateFormat.remove(); // 必须清理 }7. Java内存模型JMM核心要点7.1 happens-before原则程序顺序规则同一线程中的操作按程序顺序执行锁规则解锁操作先于后续的加锁操作volatile规则volatile写操作先于后续的读操作线程启动规则Thread.start()先于线程中的任何操作线程终止规则线程中的所有操作先于终止检测传递性规则A先于BB先于C则A先于C7.2 volatile关键字的三重语义可见性保证修改立即对其他线程可见禁止指令重排序通过内存屏障实现不保证原子性如count仍需同步典型应用场景// 状态标志位 private volatile boolean running true; public void stop() { running false; } public void run() { while(running) { // 执行任务 } }8. 并发编程最佳实践命名规范给线程和线程池设置有意义的名字new ThreadFactoryBuilder() .setNameFormat(file-process-%d) .setUncaughtExceptionHandler((t, e) - log.error(Thread {} failed, t.getName(), e)) .build();异常处理线程池任务必须捕获异常executor.submit(() - { try { // 业务代码 } catch (Exception e) { log.error(Task failed, e); } });性能优化减小锁粒度如ConcurrentHashMap的分段锁读写分离使用ReadWriteLock无锁编程CAS操作调试技巧使用jstack分析线程状态用VisualVM监控线程争用设置-XX:PrintConcurrentLocks打印锁信息常见坑点不要在同步块内调用外部方法可能导致死锁避免过度同步缩小同步范围警惕对象发布如通过public static Map暴露内部状态