ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java多线程编程:从基础概念到实战应用

2026/9/15 0:19:14 拓冰建站 浏览量
Java多线程编程:从基础概念到实战应用 1. Java多线程基础概念解析第一次接触Java多线程时我完全被各种术语和概念搞晕了。经过多年实际项目开发现在回头看理解多线程其实可以从一个生活场景开始想象你在厨房做饭一边煮汤一边炒菜这就是典型的多任务场景。在Java中每个烹饪动作就是一个线程。1.1 进程与线程的本质区别操作系统中最小的执行单位是线程而进程则是资源的集合。用公司来比喻进程就像一家公司拥有办公场地、资金等资源线程就像公司里的员工真正干活的是他们单线程进程就是个体户老板员工都是一个人在Java中每个运行的程序都是一个JVM进程main()方法就是主线程。我们通过代码验证这个特性public class MainThreadDemo { public static void main(String[] args) { System.out.println(当前线程名称 Thread.currentThread().getName()); System.out.println(当前线程ID Thread.currentThread().getId()); } }运行后会输出类似当前线程名称main 当前线程ID11.2 多线程的适用场景不是所有情况都适合用多线程。根据我的经验这些场景特别适合IO密集型操作如文件读写、网络请求计算密集型任务需要多核CPU并行计算异步处理日志记录、消息推送等后台任务GUI应用保持界面响应同时处理任务重要提示在单核CPU上多线程并不会真正并行执行只是通过时间片轮转模拟并发。真正的并行需要多核CPU支持。2. Java线程创建与生命周期2.1 三种创建线程的方式方式一继承Thread类class MyThread extends Thread { Override public void run() { System.out.println(线程运行中 getName()); } } // 使用示例 MyThread thread new MyThread(); thread.start();方式二实现Runnable接口class MyRunnable implements Runnable { Override public void run() { System.out.println(线程运行中 Thread.currentThread().getName()); } } // 使用示例 Thread thread new Thread(new MyRunnable()); thread.start();方式三使用Lambda表达式Java8new Thread(() - { System.out.println(Lambda线程 Thread.currentThread().getName()); }).start();开发建议优先选择实现Runnable接口的方式因为Java不支持多重继承使用接口更灵活。2.2 线程状态转换详解Java线程的生命周期包含6种状态NEW刚创建未启动RUNNABLE可运行状态包括正在运行和就绪BLOCKED等待监视器锁WAITING无限期等待TIMED_WAITING有限期等待TERMINATED线程终止状态转换示意图NEW → start() → RUNNABLE RUNNABLE → wait() → WAITING WAITING → notify() → RUNNABLE RUNNABLE → sleep(100) → TIMED_WAITING RUNNABLE → synchronized → BLOCKED RUNNABLE → run()结束 → TERMINATED查看线程状态的代码示例Thread thread new Thread(() - { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(创建后状态 thread.getState()); // NEW thread.start(); System.out.println(启动后状态 thread.getState()); // RUNNABLE Thread.sleep(500); System.out.println(睡眠中状态 thread.getState()); // TIMED_WAITING thread.join(); System.out.println(结束后状态 thread.getState()); // TERMINATED3. 线程同步与锁机制3.1 同步问题的根源多线程并发时会出现问题的根本原因是共享变量的可见性和操作原子性。看这个典型例子class Counter { private int count 0; public void add() { count; } public int get() { return count; } }当多个线程同时调用add()时最终结果往往会小于预期。因为count实际上包含三个操作读取count值值1写回count3.2 synchronized关键字解决上述问题的最基本方式是使用synchronizedpublic synchronized void add() { count; }或者使用同步代码块public void add() { synchronized(this) { count; } }注意事项synchronized是重量级锁在JDK1.6后进行了优化但在高并发场景下仍需谨慎使用。3.3 volatile关键字volatile解决的是可见性问题但不保证原子性class Shared { private volatile boolean flag false; public void setFlag() { flag true; } public boolean getFlag() { return flag; } }适用场景状态标志位如开关控制单例模式的双重检查锁定4. 线程间通信4.1 wait/notify机制典型的生产者-消费者模式实现class MessageQueue { private String message; private boolean empty true; public synchronized String take() { while(empty) { try { wait(); } catch (InterruptedException e) {} } empty true; notifyAll(); return message; } public synchronized void put(String message) { while(!empty) { try { wait(); } catch (InterruptedException e) {} } empty false; this.message message; notifyAll(); } }4.2 使用BlockingQueue更现代的实现方式是使用java.util.concurrent包中的BlockingQueueBlockingQueueString queue new ArrayBlockingQueue(10); // 生产者 new Thread(() - { try { queue.put(消息1); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 消费者 new Thread(() - { try { String message queue.take(); System.out.println(收到 message); } catch (InterruptedException e) { e.printStackTrace(); } }).start();5. 线程池最佳实践5.1 为什么需要线程池直接创建线程的问题创建销毁线程开销大线程数量不受控可能导致资源耗尽缺乏统一管理5.2 ThreadPoolExecutor详解核心参数corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime空闲线程存活时间workQueue任务队列threadFactory线程工厂handler拒绝策略创建示例ThreadPoolExecutor executor new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数 60, // 空闲时间 TimeUnit.SECONDS, new ArrayBlockingQueue(10), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );5.3 四种预定义线程池FixedThreadPool固定大小CachedThreadPool可缓存线程池SingleThreadExecutor单线程ScheduledThreadPool定时任务实际经验阿里巴巴开发规范建议使用ThreadPoolExecutor构造函数创建避免使用Executors工厂方法因为后者可能隐藏参数设置问题。6. 常见问题排查6.1 线程死锁死锁产生的四个必要条件互斥条件请求与保持不剥夺条件循环等待诊断死锁的方法// 1. 使用jstack命令 // 2. 代码检测 ThreadMXBean bean ManagementFactory.getThreadMXBean(); long[] threadIds bean.findDeadlockedThreads(); if (threadIds ! null) { ThreadInfo[] infos bean.getThreadInfo(threadIds); for (ThreadInfo info : infos) { System.out.println(死锁线程 info.getThreadName()); } }6.2 内存泄漏线程相关的内存泄漏常见原因静态集合持有线程引用线程局部变量未清理线程池未正确关闭排查工具VisualVMMAT内存分析工具JProfiler7. Java内存模型(JMM)深入7.1 主内存与工作内存JMM规定所有变量存储在主内存每个线程有自己的工作内存线程不能直接读写主内存变量7.2 happens-before原则重要的happens-before规则程序顺序规则锁规则volatile规则线程启动规则线程终止规则中断规则终结器规则传递性8. 并发工具类实战8.1 CountDownLatch典型应用场景多任务并行执行等待所有任务完成CountDownLatch latch new CountDownLatch(3); for (int i 0; i 3; i) { new Thread(() - { try { // 模拟任务执行 Thread.sleep((long)(Math.random() * 1000)); System.out.println(Thread.currentThread().getName() 完成); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } latch.await(); System.out.println(所有任务已完成);8.2 CyclicBarrier与CountDownLatch的区别可重复使用所有线程互相等待CyclicBarrier barrier new CyclicBarrier(3, () - { System.out.println(所有线程已到达屏障点); }); for (int i 0; i 3; i) { new Thread(() - { try { System.out.println(Thread.currentThread().getName() 到达屏障); barrier.await(); System.out.println(Thread.currentThread().getName() 继续执行); } catch (Exception e) { e.printStackTrace(); } }).start(); }9. 性能优化技巧9.1 减少锁竞争优化策略缩小同步代码块范围使用读写锁(ReentrantReadWriteLock)使用无锁数据结构(Atomic类)锁分离技术9.2 避免虚假共享典型例子class FalseSharing { volatile long x; // 可能和下一个变量在同一个缓存行 volatile long y; }解决方案填充(Padding)使用Contended注解(Java8)10. Java并发编程发展趋势10.1 虚拟线程(Project Loom)Java19引入的轻量级线程特性由JVM管理调度上下文切换开销小创建数量可达百万级try (var executor Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() - { System.out.println(虚拟线程运行中); }); }10.2 结构化并发(Java21预览)新的并发编程范式try (var scope new StructuredTaskScope.ShutdownOnFailure()) { FutureString user scope.fork(() - findUser()); FutureInteger order scope.fork(() - fetchOrder()); scope.join(); scope.throwIfFailed(); return new Response(user.resultNow(), order.resultNow()); }