深入浅出 Java 多线程:从线程生命周期到并发安全

(一)线程池核心知识

  1. 线程池原理、创建原因与方式

    • 原理:通过统一管理线程资源,实现线程复用避免频繁创建 / 销毁线程的性能损耗,同时对线程执行流程进行调度与监控
    • 创建原因:减少线程生命周期管理开销,提升系统响应速度;控制并发线程数量,防止资源耗尽;便于线程执行状态监控与异常处理。
    • 创建方式:主要有ThreadPoolExecutorThreadScheduledExecutorForkJoinPool三种核心方式。
    1. ThreadPoolExecutor方式创建
      package thread;
      
      import java.util.concurrent.ArrayBlockingQueue;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      
      public class ThreadPoolExecutorDemo {
      
          private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
                  2,//corePoolSize:核心线程数
                  5,//maximumPoolSize:最大线程数
                  60,//keepAliveTime:非核心线程空闲存活时间
                  TimeUnit.SECONDS,//时间单位
                  new ArrayBlockingQueue<>(10),//任务队列(容量10)
                  Executors.defaultThreadFactory(),//线程工厂
                  new ThreadPoolExecutor.AbortPolicy()//拒绝策略(默认:抛异常)
          );
      
          public static void main(String[] args) {
              //Lambda表达式写法
      //        executor.execute(() -> System.out.println("执行任务:"+Thread.currentThread().getName()));
              //正常匿名内部类写法
              executor.execute(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("执行任务:" + Thread.currentThread().getName());
                  }
              });
              executor.shutdown();//关闭线程池
          }
      }
      
    2. ThreadScheduledExecutor创建线程方式
      package thread;
      import java.util.Date;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      /**
       * 使用Lambda表达式写法
       */
      public class ScheduledExecutorLambdaDemo {
      
          // 创建ScheduledExecutorService(核心线程数为2)
          // 实际是返回ThreadScheduledExecutor的实例
          private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2);
          public static void main(String[] args) {
              // 1. 延迟执行任务(延迟3秒后执行一次)
              System.out.println("提交延迟任务:" + new Date());
              scheduledExecutor.schedule(() -> {
                  System.out.println("延迟任务执行:" + new Date());
              }, 3, TimeUnit.SECONDS);
              
              // 2. 周期性执行任务(初始延迟2秒,之后每4秒执行一次)
              // 注意:周期是以上一次任务结束时间开始计算
              System.out.println("提交周期性任务:" + new Date());
              scheduledExecutor.scheduleAtFixedRate(() -> {
                  System.out.println("周期性任务执行:" + new Date());
                  try {
                      // 模拟任务执行耗时1秒
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }, 2, 4, TimeUnit.SECONDS);
              
              // 3. 固定延迟执行任务(初始延迟2秒,上一次任务结束后延迟3秒再执行)
              System.out.println("提交固定延迟任务:" + new Date());
              scheduledExecutor.scheduleWithFixedDelay(() -> {
                  System.out.println("固定延迟任务执行:" + new Date());
                  try {
                      // 模拟任务执行耗时1秒
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }, 2, 3, TimeUnit.SECONDS);
      
              // 运行一段时间后关闭线程池(示例:20秒后关闭)
              scheduledExecutor.schedule(new Runnable() {
                  @Override
                  public void run() {
                      scheduledExecutor.shutdown();
                      System.out.println("线程池已关闭");
                  }
              }, 20, TimeUnit.SECONDS);
      
              // 运行一段时间后关闭线程池(示例:20秒后关闭)
              scheduledExecutor.schedule(() -> {
                  scheduledExecutor.shutdown();
                  System.out.println("线程池已关闭");
              }, 20, TimeUnit.SECONDS);
          }
      }
      
      package thread;
      
      import java.util.Date;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      /**
       * 使用匿名内部类写法
       */
      public class ScheduledExecutorDemo {
      
          // 创建ScheduledExecutorService(核心线程数为2)
          // 实际是返回ThreadScheduledExecutor的实例
          private final static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2);
      
          public static void main(String[] args) {
              // 1. 延迟执行任务(延迟3秒后执行一次)
              System.out.println("提交延迟任务:" + new Date());
              scheduledExecutor.schedule(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("延迟任务执行:" + new Date());
                  }
              }, 3, TimeUnit.SECONDS);
      
              // 2. 周期性执行任务(初始延迟2秒,之后每4秒执行一次)
              System.out.println("提交周期性任务:" + new Date());
              scheduledExecutor.scheduleAtFixedRate(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("周期性任务执行:" + new Date());
                      try {
                          // 模拟任务执行耗时1秒
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }, 2, 4, TimeUnit.SECONDS);
      
              // 3. 固定延迟执行任务(初始延迟2秒,上一次任务结束后延迟3秒再执行)
              System.out.println("提交固定延迟任务:" + new Date());
              scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("固定延迟任务执行:" + new Date());
                      try {
                          // 模拟任务执行耗时1秒
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }, 2, 3, TimeUnit.SECONDS);
      
              // 运行一段时间后关闭线程池(示例:20秒后关闭)
              // 使用传统匿名内部类写法替代lambda表达式
              scheduledExecutor.schedule(new Runnable() {
                  @Override
                  public void run() {
                      scheduledExecutor.shutdown();
                      System.out.println("线程池已关闭");
                  }
              }, 20, TimeUnit.SECONDS);
          }
      }
      
    3. ForkJoinPool创建线程
      package thread;
      
      import java.util.concurrent.ForkJoinPool;
      import java.util.concurrent.RecursiveTask;
      
      public class ForkJoinPoolDemo {
          public static void main(String[] args) {
              // ==================== 1. 创建ForkJoinPool的几种方式 ====================
              
              // 1.1 使用默认构造(推荐)
              // 核心线程数 = CPU核心数,使用默认线程工厂和异常处理器
              ForkJoinPool defaultPool = new ForkJoinPool();
              
              // 1.2 手动指定并行度(核心线程数)
              // 参数:parallelism - 并行度(通常设为CPU核心数)
              int parallelism = Runtime.getRuntime().availableProcessors(); // 获取CPU核心数
              ForkJoinPool customPool = new ForkJoinPool(parallelism);
              
              // 1.3 完整参数构造(自定义线程工厂、异常处理器等)
              /*
              ForkJoinPool fullPool = new ForkJoinPool(
                  parallelism,                // 并行度(核心线程数)
                  ForkJoinPool.defaultForkJoinWorkerThreadFactory, // 线程工厂
                  null,                       // 未捕获异常处理器(null使用默认)
                  false                       // 是否为异步模式(通常用false)
              );
              */
              
              
              // ==================== 2. 示例:使用ForkJoinPool计算数组总和 ====================
              int[] array = new int[1000];
              for (int i = 0; i < array.length; i++) {
                  array[i] = i + 1; // 填充1~1000
              }
              
              // 创建任务(继承RecursiveTask,有返回值)
              SumTask task = new SumTask(array, 0, array.length);
              
              // 提交任务并获取结果
              Integer result = customPool.invoke(task);
              System.out.println("数组总和:" + result); // 预期结果:500500
              
              // 关闭线程池
              customPool.shutdown();
          }
          
          // 自定义分治任务(计算数组指定区间的和)
          static class SumTask extends RecursiveTask<Integer> {
              private static final int THRESHOLD = 100; // 任务拆分阈值(小于此值直接计算)
              private int[] array;
              private int start;
              private int end;
              
              public SumTask(int[] array, int start, int end) {
                  this.array = array;
                  this.start = start;
                  this.end = end;
              }
              
              @Override
              protected Integer compute() {
                  // 如果任务足够小,直接计算
                  if (end - start <= THRESHOLD) {
                      int sum = 0;
                      for (int i = start; i < end; i++) {
                          sum += array[i];
                      }
                      return sum;
                  }
                  
                  // 否则拆分任务
                  int mid = (start + end) / 2;
                  SumTask leftTask = new SumTask(array, start, mid);
                  SumTask rightTask = new SumTask(array, mid, end);
                  
                  // 并行执行子任务
                  leftTask.fork();  // 拆分左任务
                  rightTask.fork(); // 拆分右任务
                  
                  // 合并结果
                  return leftTask.join() + rightTask.join();
              }
          }
      }
      
  2. 线程池核心参数与大小配置

    Snipaste_2025-09-16_11-05-08

    • 核心参数ThreadPoolExecutor的核心参数包括corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲线程存活时间)、TimeUnit(存活时间单位)、workQueue(任务缓存队列)、threadFactory(线程创建工厂)、RejectedExecutionHandler(任务拒绝策略)。
    • 参数工作逻辑:
      • 线程数 <corePoolSize:直接创建线程处理请求。
      • 线程数 ≥corePoolSize:将请求存入workQueue,空闲核心线程从队列取任务。
      • workQueue满:创建非核心线程,直至线程数达maximumPoolSize
      • 线程数>maximumPoolSize:触发RejectedExecutionHandler处理拒绝任务。
    • 线程池大小配置策略:
      • CPU 密集型任务:配置为CPU核心数 + 1,减少线程切换损耗。
      • IO 密集型任务:可选CPU核心数 × 2,或按(线程等待时间/CPU时间 + 1) × CPU核心数计算。
      • 混合型任务:拆分为 CPU 密集型IO 密集型子任务,分别用独立线程池处理。

(二)线程生命周期与安全

  1. 线程生命周期:包含新建可运行运行阻塞/等待终止五个阶段,状态定义在 Thread.State 枚举中。

    状态枚举 核心含义 进入方式示例
    NEW 线程对象已创建(new Thread()),但未调用start(),未分配系统资源 Thread t = new Thread();
    RUNNABLE 调用start()后,线程具备运行条件(要么正在 CPU 执行,要么等待 CPU 调度) t.start();
    BLOCKED 竞争同步锁失败(如进入synchronized代码块但锁被占用),被迫阻塞 线程 A 未释放锁时,线程 B 进入同一synchronized块
    WAITING 无限期等待被唤醒,无超时时间,需其他线程主动触发唤醒 t.wait()、Thread.join()(无参)
    TIMED_WAITING 限时等待,超时后自动唤醒,无需其他线程干预 Thread.sleep(1000)、t.wait(1000)
    TERMINATED 线程执行完run()因异常终止,生命周期彻底结束 run()方法执行完毕、未捕获异常抛出

    image

    补充:3 种 “阻塞 / 等待状态” 的关键区别(避免混淆)

    状态类型 唤醒条件 是否释放已持有锁 典型场景
    BLOCKED 锁被释放(其他线程解锁) 是(仅释放竞争的锁) 竞争synchronized
    WAITING 其他线程调用notify()/notifyAll(),或join()的线程结束 是(释放所有已持有锁) 调用无参wait()join()
    TIMED_WAITING 超时自动唤醒,或提前被notify() 是(释放所有已持有锁) sleep()、有参wait()
  2. 僵死进程:子进程退出时,父进程未处理其SIGCHLD信号,导致子进程残留僵死状态,需等待父进程 “回收”,此类进程即为僵死进程。

  3. 线程安全实现方式

    1. 互斥同步(阻塞式)
      通过锁定共享资源,保证同一时间只有一个线程访问。
      • synchronized 关键字:隐式锁,自动获取和释放。
        package thread;
        
        public class SynchronizedDemo {
        
            private int count;
            // 修饰方法
            public synchronized void increment() {
                count++;
            }
        
            // 修饰代码块
            public void increment() {
                synchronized (this) { count++; }
            }
            
        }
        
      • ReentrantLock 显式锁:手动控制锁的获取和释放,支持中断、超时等高级特性。
        package thread;
        
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;
        
        public class ReentrantLockDemo {
            private int count = 0;
            private final Lock lock = new ReentrantLock();
            public void increment() {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock(); // 必须手动释放
                }
            }
        }
        
    2. 非阻塞同步(无锁式)
      基于 CAS(Compare-And-Swap)机制,无线程阻塞,效率更高。
      package thread;
      
      import java.util.concurrent.atomic.AtomicInteger;
      
      public class AtomicIntegerDemo {
          private static final AtomicInteger atomicCount = new AtomicInteger(0);
      
          public static void increment() {
              // 原子操作,底层依赖CAS
              atomicCount.incrementAndGet();
          }
      }
      
    3. 无同步方案
      • 线程封闭:变量仅在单个线程内使用(如局部变量)。
      • 不可变对象:用 final 修饰共享对象,避免被修改(如 String)。
      • ThreadLocal:为每个线程创建独立变量副本,彻底避免共享
        package thread;
        
        public class ThreadLocalDemo {
            private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
        
            public static void increment() {
                threadLocal.set(threadLocal.get() + 1); // 每个线程操作自己的副本
            }
        }
        

(三)关键字与锁

  1. volatile 关键字

    • 核心原理
      通过底层硬件指令(如 x86 的 Lock 前缀)实现两个核心保障:
      1. 可见性:写操作会将变量从 CPU 缓存刷回主内存,读操作会直接从主内存加载,确保所有线程看到的变量值一致。
      2. 有序性:通过内存屏障(Memory Barrier)禁止编译器和 CPU 对指令重排序,避免多线程下的执行顺序混乱(如 DCL 单例中的指令重排问题)。
    • 局限性
      不保证原子性,例如 i++ 这类复合操作(读 - 改 - 写)在多线程下仍会出现数据不一致。
    • 典型使用场景
      package thread;
      
      public class VolatileDemo {
          // 1. 私有静态变量,用volatile修饰防止指令重排序
          private static volatile VolatileDemo instance;
      
          // 2. 私有构造方法,防止外部直接实例化
          private VolatileDemo() {
              // 可选:防止反射破坏单例
              if (instance != null) {
                  throw new RuntimeException("禁止通过反射创建实例");
              }
          }
      
          // 3. 公共静态方法,提供全局访问点
          public static VolatileDemo getInstance() {
              // 第一次检查:未加锁,快速判断(提高性能)
              if (instance == null) {
                  // 加锁:只在第一次初始化时进入同步块
                  synchronized (VolatileDemo.class) {
                      // 第二次检查:防止多线程并发时重复创建
                      if (instance == null) {
                          // 这里涉及三个操作:
                          // 1. 分配内存空间
                          // 2. 初始化对象
                          // 3. 将instance引用指向内存空间
                          // volatile保证这三个步骤不会被重排序
                          instance = new VolatileDemo();
                      }
                  }
              }
              return instance;
          }
      
          // 可选:防止反序列化破坏单例(如果需要序列化)
          private Object readResolve() {
              return instance;
          }
      }
      
  2. ThreadLocal 线程本地存储

    • 核心原理:
      为每个线程创建变量的独立副本,线程只操作自己的副本,彻底避免共享。底层通过线程的 ThreadLocalMap 存储数据(ThreadLocal 实例作为 key,变量副本作为 value)。
    • 适用场景:
      变量需要 “线程私有” 但又需跨方法传递的场景,例如:
      • 数据库连接(Connection):每个线程持有自己的连接,避免并发冲突。
      • Web 开发中的 Session:每个请求线程独立处理自己的会话信息。
    • OOM 风险与避免:
      线程池中的线程会被复用,若 ThreadLocal 变量不清理,会导致变量副本随线程长期存在,引发内存泄漏
      解决方法: 使用后主动调用 remove() 清理:
      package thread;
      
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      public class ThreadLocalDateFormatDemo {
          // 1. 创建ThreadLocal,初始化SimpleDateFormat(指定格式)
          private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = 
              ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
      
          // 2. 创建线程池(模拟多线程环境)
          private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2);
      
          public static void main(String[] args) {
      
              // 3. 提交10个任务,测试线程安全
              for (int i = 0; i < 10; i++) {
                  final int taskId = i;
                  scheduledExecutor.submit(() -> {
                      try {
                          // 模拟任务执行时间
                          Thread.sleep(100);
                          // 调用工具方法格式化日期
                          String formattedDate = formatDate(new Date());
                          System.out.printf("任务%d:%s(线程:%s)%n", 
                                           taskId, formattedDate, Thread.currentThread().getName());
                      } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                      }
                  });
              }
      
              // 4. 关闭线程池
              scheduledExecutor.shutdown();
              try {
                  scheduledExecutor.awaitTermination(1, TimeUnit.MINUTES);
              } catch (InterruptedException e) {
                  scheduledExecutor.shutdownNow();
              }
          }
      
          // 5. 日期格式化工具方法(线程安全)
          public static String formatDate(Date date) {
              // 获取当前线程的SimpleDateFormat副本
              SimpleDateFormat sdf = DATE_FORMATTER.get();
              try {
                  // 格式化日期(每个线程操作自己的副本,避免并发问题)
                  return sdf.format(date);
              } finally {
                  // 6. 清理ThreadLocal,避免线程池复用导致的内存泄漏
                  DATE_FORMATTER.remove();
              }
          }
      } 
      
  3. synchronized 与 volatile 区别

    对比维度 volatile synchronized
    作用范围 仅修饰变量 修饰方法、代码块(可锁定对象 / 类)
    线程阻塞 无阻塞(纯内存语义) 可能阻塞(锁竞争时进入 BLOCKED 状态)
    原子性 不保证(仅可见性 / 有序性) 保证(临界区操作原子执行)
    性能开销 极低(接近普通变量) 较高(锁获取 / 释放有开销)
    适用场景 状态标志、简单通信 复杂逻辑的互斥同步(如计数器、资源竞争)

    一句话总结:volatile 是 “轻量级” 的线程通信工具,synchronized 是 “重量级” 的互斥同步工具。

  4. synchronized 锁粒度与死锁
    锁粒度:synchronized 的锁定范围由 “锁定对象” 决定,分为:

    • 对象锁:锁定实例对象(this),修饰非静态方法或 synchronized(this) { ... } 代码块,仅影响该实例的线程访问。
    • 类锁:锁定类对象(XXX.class),修饰静态方法或 synchronized(XXX.class) { ... } 代码块,影响所有实例的线程访问。
    package thread;
    
    public class LockGranularity {
        // 对象锁(锁定当前实例)
        public synchronized void instanceMethod() { ... }
    
        // 类锁(锁定 LockGranularity.class)
        public static synchronized void staticMethod() { ... }
    
        public void blockDemo() {
            Object lock = new Object();
            synchronized (lock) { // 锁定指定对象(局部锁,粒度更细)
            ...
            }
        }
    }
    

    死锁及避免

    • 死锁场景: 多线程循环等待对方释放锁,且永不释放自己的锁。
      例如:线程 1 持有锁 A 等待锁 B线程 2 持有锁 B 等待锁 A
    • 死锁避免策略:
    1. 固定锁顺序: 所有线程按相同顺序获取锁(如先获取编号小的锁)。
    2. 设置超时时间: 使用 ReentrantLock.tryLock(timeout),超时则释放已持有的锁。
    3. 减少锁持有时间: 仅在必要代码段加锁,避免长时间持有锁。
      package thread;
      
      public class ThreadDemo01 {
          // 定义锁的顺序(A的优先级高于B)
          private static final Object LOCK_A = new Object();
          private static final Object LOCK_B = new Object();
      
          // 所有线程都先获取LOCK_A,再获取LOCK_B
          public void method1() {
              synchronized (LOCK_A) {
                  synchronized (LOCK_B) {
                      // 业务逻辑
                  }
              }
          }
      
          public void method2() {
              synchronized (LOCK_A) { // 遵循相同顺序,避免死锁
                  synchronized (LOCK_B) {
                      // 业务逻辑
                  }
              }
          }
      }
      
posted @ 2025-09-16 13:01  窝瓜小冬瓜  阅读(21)  评论(0)    收藏  举报