首页/发现新知
视界 · 深度阅读

Java多线程入门到精通:线程同步和线程池怎么学

还在为Java多线程头疼?本文用通俗易懂的方式讲透线程创建、生命周期、同步锁和线程池,帮你彻底搞懂并发安全问题的根源和解决方案,还有文件批量下载实战案例,让你快速上手多线程开发

目录
  • 1.1 本章学习目标与重点
  • 1.2 多线程核心概念
    • 1.2.1 进程与线程的区别
    • 1.2.2 为什么需要多线程
  • 1.3 线程的创建方式
    • 1.3.1 方式1:继承 Thread 类
      • 实现步骤
      • 代码实操
    • 1.3.2 方式2:实现 Runnable 接口
      • 实现步骤
      • 代码实操
      • 方式2的优势
      • 资源共享案例:多线程售票
    • 1.3.3 方式3:实现 Callable 接口(带返回值)
      • 实现步骤
      • 代码实操
  • 1.4 线程的生命周期与状态转换
    • 1.4.1 线程的六种状态
      • 1.4.2 线程状态转换图(核心流程)
        • 1.4.3 常用线程控制方法
          • 代码实操:线程的顺序执行(join 方法)
      • 1.5 线程同步与并发安全
        • 1.5.1 并发安全问题的产生
          • 1.5.2 解决方案1:同步代码块(synchronized)
            • 语法格式
            • 代码实操:同步代码块解决售票问题
          • 1.5.3 解决方案2:同步方法(synchronized)
            • 语法格式
            • 代码实操:同步方法解决售票问题
          • 1.5.4 解决方案3:Lock 锁(JUC 包)
            • 实现步骤
            • 代码实操:Lock 锁解决售票问题
        • 1.6 线程池:高效管理多线程
          • 1.6.1 为什么需要线程池
            • 1.6.2 线程池的核心参数(ThreadPoolExecutor)
              • 参数说明
            • 1.6.3 线程池的使用方式
              • 方式1:通过 Executors 工具类创建(快速使用)
              • 代码实操:固定大小线程池
              • 方式2:手动创建 ThreadPoolExecutor(推荐,更可控)
          • 1.7 实战案例:多线程实现文件批量下载
            • 1.7.1 需求分析
              • 1.7.2 代码实现
                • 1.7.3 案例总结
                • 总结

                  1.1 本章学习目标与重点

                  掌握线程的核心概念,理解进程与线程的区别和联系。

                  熟练掌握线程的三种创建方式,理解线程的生命周期及状态转换。

                  掌握线程同步与锁机制,解决多线程并发安全问题。

                  了解线程池的核心原理与使用方法,提升多线程程序性能。

                  本章重点是 线程同步机制 和 线程池的实战应用,这是多线程开发中的核心难点和高频考点。

                  1.2 多线程核心概念

                  1.2.1 进程与线程的区别

                  进程是操作系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间和系统资源。比如打开一个 Java 程序,就会启动一个进程。

                  线程是进程的执行单元,是 CPU 调度和执行的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和资源。

                  对比维度 进程 线程
                  资源分配 拥有独立的内存空间和资源 共享所属进程的内存和资源
                  开销成本 创建和销毁开销大 创建和销毁开销小
                  调度方式 由操作系统内核调度 由进程内部调度
                  独立性 进程之间相互独立 线程之间共享资源,依赖性强

                  核心结论:线程是轻量级的进程,多线程编程可以充分利用 CPU 资源,提升程序的并发执行效率。

                  1.2.2 为什么需要多线程

                  在单线程程序中,代码是串行执行的,当遇到耗时操作(如文件读写、网络请求)时,程序会阻塞等待,造成 CPU 资源浪费。
                  多线程的优势体现在以下场景:

                  1. 后台任务处理:如电商系统的订单超时取消、数据同步等。
                  2. 异步操作:如用户注册后发送短信验证码,不阻塞主流程。
                  3. 提高 CPU 利用率:如计算密集型任务,多线程可以充分利用多核 CPU。

                  1.3 线程的创建方式

                  1.3.1 方式1:继承 Thread 类

                  实现步骤

                  ① 定义一个类继承 Thread 类。

                  ② 写 run() 方法,编写线程执行的业务逻辑。

                  ③ 创建线程对象,调用 start() 方法启动线程。

                  代码实操

                  /**
                   * 继承Thread类创建线程
                   */
                  public class MyThread extends Thread {
                      // 线程名称
                      private String threadName;
                  
                      public MyThread(String threadName) {
                          this.threadName = threadName;
                      }
                  
                      // 重写run方法,定义线程执行逻辑
                      @Override
                      public void run() {
                          for (int i = 1; i <= 5; i++) {
                              System.out.println(threadName + " 执行:" + i);
                              // 模拟线程执行耗时操作
                              try {
                                  Thread.sleep(500);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  
                      public static void main(String[] args) {
                          // 创建线程对象
                          MyThread thread1 = new MyThread("线程A");
                          MyThread thread2 = new MyThread("线程B");
                  
                          // 启动线程:注意是调用start()方法,不是run()方法
                          thread1.start();
                          thread2.start();
                      }
                  }
                  

                  输出结果(顺序不固定,体现线程并发执行)

                  线程A 执行:1
                  线程B 执行:1
                  线程A 执行:2
                  线程B 执行:2
                  线程A 执行:3
                  线程B 执行:3
                  线程A 执行:4
                  线程B 执行:4
                  线程A 执行:5
                  线程B 执行:5
                  

                  注意事项:启动线程必须调用 start() 方法,直接调用 run() 方法会变成普通方法的串行执行。

                  1.3.2 方式2:实现 Runnable 接口

                  实现步骤

                  ① 定义一个类实现 Runnable 接口。

                  ② 重写 run() 方法,编写线程执行逻辑。

                  ③ 创建 Runnable 实现类对象,作为 Thread 构造方法的参数。

                  ④ 调用 start() 方法启动线程。

                  代码实操

                  /**
                   * 实现Runnable接口创建线程
                   */
                  public class MyRunnable implements Runnable {
                      private String taskName;
                  
                      public MyRunnable(String taskName) {
                          this.taskName = taskName;
                      }
                  
                      @Override
                      public void run() {
                          for (int i = 1; i <= 5; i++) {
                              System.out.println(taskName + " 执行:" + i);
                              try {
                                  Thread.sleep(500);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  
                      public static void main(String[] args) {
                          // 创建任务对象
                          MyRunnable task1 = new MyRunnable("任务A");
                          MyRunnable task2 = new MyRunnable("任务B");
                  
                          // 创建线程对象,传入任务
                          Thread thread1 = new Thread(task1);
                          Thread thread2 = new Thread(task2);
                  
                          // 启动线程
                          thread1.start();
                          thread2.start();
                      }
                  }
                  

                  方式2的优势

                  1. 避免单继承的局限性:Java 是单继承机制,继承 Thread 类后无法再继承其他类,而实现 Runnable 接口可以。
                  2. 资源共享:多个线程可以共享同一个 Runnable 任务对象,适合多线程处理同一份资源的场景。

                  资源共享案例:多线程售票

                  /**
                   * 多线程共享资源:模拟售票系统
                   */
                  public class TicketRunnable implements Runnable {
                      // 共享资源:10张票
                      private int ticketNum = 10;
                  
                      @Override
                      public void run() {
                          while (ticketNum > 0) {
                              // 模拟售票耗时
                              try {
                                  Thread.sleep(300);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 张票");
                          }
                      }
                  
                      public static void main(String[] args) {
                          // 一个任务对象,多个线程共享
                          TicketRunnable ticketTask = new TicketRunnable();
                  
                          // 三个线程模拟三个售票窗口
                          Thread window1 = new Thread(ticketTask, "窗口1");
                          Thread window2 = new Thread(ticketTask, "窗口2");
                          Thread window3 = new Thread(ticketTask, "窗口3");
                  
                          window1.start();
                          window2.start();
                          window3.start();
                      }
                  }
                  

                  输出结果(存在线程安全问题,后面会解决)

                  窗口1 售出第 10 张票
                  窗口2 售出第 9 张票
                  窗口3 售出第 8 张票
                  窗口1 售出第 7 张票
                  窗口2 售出第 6 张票
                  窗口3 售出第 5 张票
                  窗口1 售出第 4 张票
                  窗口2 售出第 3 张票
                  窗口3 售出第 2 张票
                  窗口1 售出第 1 张票
                  

                  1.3.3 方式3:实现 Callable 接口(带返回值)

                  实现步骤

                  ① 定义一个类实现 Callable 接口,V 是返回值类型。

                  ② 重写 call() 方法,编写线程执行逻辑,该方法可以抛出异常。

                  ③ 创建 Callable 实现类对象,通过 FutureTask 包装。

                  ④ 将 FutureTask 作为 Thread 构造方法参数,启动线程。

                  ⑤ 调用 FutureTask 的 get() 方法获取返回值。

                  代码实操

                  import java.util.concurrent.Callable;
                  import java.util.concurrent.FutureTask;
                  
                  /**
                   * 实现Callable接口创建线程(带返回值)
                   */
                  public class MyCallable implements Callable {
                      // 计算1到n的累加和
                      private int n;
                  
                      public MyCallable(int n) {
                          this.n = n;
                      }
                  
                      @Override
                      public Integer call() throws Exception {
                          int sum = 0;
                          for (int i = 1; i <= n; i++) {
                              sum += i;
                              System.out.println("计算中:" + i + ",当前和为:" + sum);
                              Thread.sleep(200);
                          }
                          return sum;
                      }
                  
                      public static void main(String[] args) throws Exception {
                          // 创建Callable任务
                          MyCallable callableTask = new MyCallable(10);
                  
                          // 用FutureTask包装,用于获取返回值
                          FutureTask futureTask = new FutureTask(callableTask);
                  
                          // 创建线程并启动
                          Thread thread = new Thread(futureTask);
                          thread.start();
                  
                          // 获取返回值:get()方法会阻塞,直到线程执行完成
                          Integer result = futureTask.get();
                          System.out.println("1到10的累加和为:" + result);
                      }
                  }
                  

                  输出结果

                  计算中:1,当前和为:1
                  计算中:2,当前和为:3
                  计算中:3,当前和为:6
                  计算中:4,当前和为:10
                  计算中:5,当前和为:15
                  计算中:6,当前和为:21
                  计算中:7,当前和为:28
                  计算中:8,当前和为:36
                  计算中:9,当前和为:45
                  计算中:10,当前和为:55
                  1到10的累加和为:55
                  

                  核心结论:三种创建方式对比

                  创建方式 优点 缺点
                  继承 Thread 编程简单,直接使用 this 获取线程 单继承局限,无法共享资源
                  实现 Runnable 避免单继承,支持资源共享 无返回值,无法抛出受检异常
                  实现 Callable 有返回值,支持异常抛出 编程相对复杂

                  1.4 线程的生命周期与状态转换

                  1.4.1 线程的六种状态

                  Java 中线程的生命周期包含六种状态,定义在 Thread.State 枚举中:

                  NEW(新建状态):线程对象已创建,但未调用 start() 方法。

                  RUNNABLE(可运行状态):调用 start() 方法后,线程处于就绪或运行中。

                  • 就绪状态:线程等待 CPU 调度。
                  • 运行状态:线程获取 CPU 资源,执行 run() 方法。
                  1. BLOCKED(阻塞状态):线程等待获取锁资源时进入该状态。
                  2. WAITING(等待状态):线程调用 wait()、join() 等方法后进入,需要被其他线程唤醒。
                  3. TIMED_WAITING(超时等待状态):线程调用 sleep(long)、wait(long) 等方法后进入,超时自动唤醒。
                  4. TERMINATED(终止状态):线程执行完毕或异常终止。

                  1.4.2 线程状态转换图(核心流程)

                  NEW → RUNNABLE:调用 start() 方法
                  RUNNABLE → BLOCKED:竞争锁失败
                  BLOCKED → RUNNABLE:获取到锁资源
                  RUNNABLE → WAITING:调用 wait()/join() 方法
                  WAITING → RUNNABLE:其他线程调用 notify()/notifyAll() 方法
                  RUNNABLE → TIMED_WAITING:调用 sleep(long)/wait(long) 方法
                  TIMED_WAITING → RUNNABLE:超时自动唤醒或被其他线程唤醒
                  RUNNABLE → TERMINATED:run() 方法执行完毕或异常终止
                  

                  1.4.3 常用线程控制方法

                  方法 作用 注意事项
                  start() 启动线程 一个线程只能调用一次
                  sleep(long millis) 让线程休眠指定时间 不会释放持有的锁资源
                  wait() 让线程进入等待状态 必须在同步代码块中调用,会释放锁
                  notify() 唤醒一个等待的线程 必须在同步代码块中调用
                  notifyAll() 唤醒所有等待的线程 必须在同步代码块中调用
                  join() 等待该线程执行完毕 可以实现线程的顺序执行

                  代码实操:线程的顺序执行(join 方法)

                  /**
                   * 使用join方法实现线程顺序执行
                   */
                  public class ThreadJoinDemo {
                      public static void main(String[] args) throws InterruptedException {
                          Thread threadA = new Thread(() -> {
                              for (int i = 1; i  {
                              for (int i = 1; i <= 3; i++) {
                                  System.out.println("线程B执行:" + i);
                                  try {
                                      Thread.sleep(500);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              }
                          });
                  
                          // 启动线程A
                          threadA.start();
                          // 等待线程A执行完毕,再启动线程B
                          threadA.join();
                          threadB.start();
                      }
                  }
                  

                  输出结果(线程A执行完才会执行线程B)

                  线程A执行:1
                  线程A执行:2
                  线程A执行:3
                  线程B执行:1
                  线程B执行:2
                  线程B执行:3
                  

                  1.5 线程同步与并发安全

                  1.5.1 并发安全问题的产生

                  当多个线程同时操作共享资源时,会导致数据不一致的问题。比如前面的售票案例,在极端情况下会出现超卖或重复售票的问题。

                  问题复现:多线程售票的并发安全问题

                  public class UnsafeTicketDemo implements Runnable {
                      private int ticketNum = 10;
                  
                      @Override
                      public void run() {
                          while (ticketNum > 0) {
                              // 模拟网络延迟,放大并发问题
                              try {
                                  Thread.sleep(100);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              // 核心问题:判断和操作不是原子性的
                              System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 张票");
                          }
                      }
                  
                      public static void main(String[] args) {
                          UnsafeTicketDemo task = new UnsafeTicketDemo();
                          new Thread(task, "窗口1").start();
                          new Thread(task, "窗口2").start();
                          new Thread(task, "窗口3").start();
                      }
                  }
                  

                  可能出现的错误结果

                  窗口1 售出第 10 张票
                  窗口2 售出第 10 张票
                  窗口3 售出第 9 张票
                  窗口1 售出第 8 张票
                  窗口2 售出第 7 张票
                  窗口3 售出第 6 张票
                  窗口1 售出第 5 张票
                  窗口2 售出第 4 张票
                  窗口3 售出第 3 张票
                  窗口1 售出第 2 张票
                  窗口2 售出第 1 张票
                  窗口3 售出第 0 张票
                  

                  可以看到出现了重复售票和售出0号票的问题,这就是典型的并发安全问题。

                  1.5.2 解决方案1:同步代码块(synchronized)

                  同步代码块的核心是锁机制,通过 synchronized 关键字锁定一个对象,保证同一时间只有一个线程能执行代码块中的内容。

                  语法格式

                  synchronized (锁对象) {
                      // 需要同步的代码(操作共享资源的代码)
                  }
                  

                  代码实操:同步代码块解决售票问题

                  public class SafeTicketDemo1 implements Runnable {
                      private int ticketNum = 10;
                      // 定义锁对象:必须是多个线程共享的对象
                      private final Object lock = new Object();
                  
                      @Override
                      public void run() {
                          while (true) {
                              synchronized (lock) {
                                  if (ticketNum > 0) {
                                      try {
                                          Thread.sleep(100);
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 张票");
                                  } else {
                                      break;
                                  }
                              }
                          }
                      }
                  
                      public static void main(String[] args) {
                          SafeTicketDemo1 task = new SafeTicketDemo1();
                          new Thread(task, "窗口1").start();
                          new Thread(task, "窗口2").start();
                          new Thread(task, "窗口3").start();
                      }
                  }
                  

                  输出结果(无重复、无超卖,解决并发安全问题)

                  窗口1 售出第 10 张票
                  窗口1 售出第 9 张票
                  窗口2 售出第 8 张票
                  窗口2 售出第 7 张票
                  窗口3 售出第 6 张票
                  窗口3 售出第 5 张票
                  窗口1 售出第 4 张票
                  窗口1 售出第 3 张票
                  窗口2 售出第 2 张票
                  窗口2 售出第 1 张票
                  

                  注意事项:锁对象必须是多个线程共享的对象,否则无法实现同步效果。

                  1.5.3 解决方案2:同步方法(synchronized)

                  同步方法是将 synchronized 关键字修饰在方法上,等价于锁定当前对象(this)。

                  语法格式

                  public synchronized 返回值类型 方法名(参数列表) {
                      // 需要同步的代码
                  }
                  

                  代码实操:同步方法解决售票问题

                  public class SafeTicketDemo2 implements Runnable {
                      private int ticketNum = 10;
                  
                      @Override
                      public void run() {
                          while (true) {
                              if (!sellTicket()) {
                                  break;
                              }
                          }
                      }
                  
                      // 同步方法:锁定的是this对象
                      private synchronized boolean sellTicket() {
                          if (ticketNum > 0) {
                              try {
                                  Thread.sleep(100);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 张票");
                              return true;
                          } else {
                              return false;
                          }
                      }
                  
                      public static void main(String[] args) {
                          SafeTicketDemo2 task = new SafeTicketDemo2();
                          new Thread(task, "窗口1").start();
                          new Thread(task, "窗口2").start();
                          new Thread(task, "窗口3").start();
                      }
                  }
                  

                  1.5.4 解决方案3:Lock 锁(JUC 包)

                  java.util.concurrent.locks.Lock 是 JDK 5 新增的锁机制,相比 synchronized 更加灵活,可以手动控制锁的获取和释放。

                  常用实现类是 ReentrantLock(可重入锁)。

                  实现步骤

                  ① 创建 ReentrantLock 对象。

                  ② 在需要同步的代码前调用 lock() 方法获取锁。

                  ③ 在 finally 块中调用 unlock() 方法释放锁,确保锁一定会被释放。

                  代码实操:Lock 锁解决售票问题

                  import java.util.concurrent.locks.ReentrantLock;
                  
                  public class SafeTicketDemo3 implements Runnable {
                      private int ticketNum = 10;
                      // 创建可重入锁对象
                      private final ReentrantLock lock = new ReentrantLock();
                  
                      @Override
                      public void run() {
                          while (true) {
                              // 获取锁
                              lock.lock();
                              try {
                                  if (ticketNum > 0) {
                                      Thread.sleep(100);
                                      System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 张票");
                                  } else {
                                      break;
                                  }
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              } finally {
                                  // 释放锁:必须放在finally块中
                                  lock.unlock();
                              }
                          }
                      }
                  
                      public static void main(String[] args) {
                          SafeTicketDemo3 task = new SafeTicketDemo3();
                          new Thread(task, "窗口1").start();
                          new Thread(task, "窗口2").start();
                          new Thread(task, "窗口3").start();
                      }
                  }
                  

                  核心结论:synchronized 和 Lock 的对比

                  特性 synchronized Lock
                  锁获取释放 自动获取和释放 手动调用 lock()/unlock()
                  灵活性 低 高(支持公平锁/非公平锁)
                  可中断性 不支持 支持
                  条件变量 不支持 支持(Condition)

                  1.6 线程池:高效管理多线程

                  1.6.1 为什么需要线程池

                  频繁创建和销毁线程会消耗大量系统资源,线程池可以复用线程,降低系统开销。线程池的核心优势:

                  1. 降低资源消耗:复用已创建的线程,减少创建和销毁线程的开销。
                  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
                  3. 便于线程管理:可以控制最大并发数,避免线程过多导致资源耗尽。

                  1.6.2 线程池的核心参数(ThreadPoolExecutor)

                  Java 中线程池的核心实现类是 ThreadPoolExecutor,其构造方法包含 7 个核心参数:

                  public ThreadPoolExecutor(
                      int corePoolSize,                  // 核心线程数
                      int maximumPoolSize,               // 最大线程数
                      long keepAliveTime,                // 非核心线程空闲存活时间
                      TimeUnit unit,                     // 时间单位
                      BlockingQueue workQueue, // 任务队列
                      ThreadFactory threadFactory,       // 线程工厂
                      RejectedExecutionHandler handler   // 拒绝策略
                  )
                  

                  参数说明

                  1. 核心线程数:线程池长期保持的线程数量,即使空闲也不会销毁。
                  2. 最大线程数:线程池允许创建的最大线程数。
                  3. 空闲存活时间:非核心线程空闲超过该时间会被销毁。
                  4. 任务队列:存放等待执行的任务,当核心线程都在忙时,任务会进入队列。
                  5. 拒绝策略:当任务队列满且线程数达到最大时,如何处理新任务。

                  1.6.3 线程池的使用方式

                  方式1:通过 Executors 工具类创建(快速使用)

                  Executors 提供了几种常用的线程池创建方法:

                  • newFixedThreadPool(int nThreads):固定大小的线程池。
                  • newCachedThreadPool():缓存线程池,线程数可动态调整。
                  • newSingleThreadExecutor():单线程池,保证任务顺序执行。

                  代码实操:固定大小线程池

                  import java.util.concurrent.ExecutorService;
                  import java.util.concurrent.Executors;
                  
                  /**
                   * 固定大小线程池的使用
                   */
                  public class ThreadPoolDemo {
                      public static void main(String[] args) {
                          // 1. 创建固定大小的线程池,核心线程数=最大线程数=3
                          ExecutorService executorService = Executors.newFixedThreadPool(3);
                  
                          // 2. 提交10个任务
                          for (int i = 1; i  {
                                  System.out.println("任务" + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");
                                  try {
                                      Thread.sleep(500);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              });
                          }
                  
                          // 3. 关闭线程池
                          executorService.shutdown();
                      }
                  }
                  

                  输出结果(3个线程复用执行10个任务)

                  任务1 由线程 pool-1-thread-1 执行
                  任务2 由线程 pool-1-thread-2 执行
                  任务3 由线程 pool-1-thread-3 执行
                  任务4 由线程 pool-1-thread-1 执行
                  任务5 由线程 pool-1-thread-2 执行
                  任务6 由线程 pool-1-thread-3 执行
                  任务7 由线程 pool-1-thread-1 执行
                  任务8 由线程 pool-1-thread-2 执行
                  任务9 由线程 pool-1-thread-3 执行
                  任务10 由线程 pool-1-thread-1 执行
                  

                  方式2:手动创建 ThreadPoolExecutor(推荐,更可控)

                  import java.util.concurrent.*;
                  
                  /**
                   * 手动创建线程池(推荐方式)
                   */
                  public class CustomThreadPoolDemo {
                      public static void main(String[] args) {
                          // 1. 定义线程池参数
                          int corePoolSize = 2;
                          int maximumPoolSize = 5;
                          long keepAliveTime = 60;
                          TimeUnit unit = TimeUnit.SECONDS;
                          // 任务队列:容量为3的阻塞队列
                          BlockingQueue workQueue = new ArrayBlockingQueue(3);
                          // 线程工厂:默认线程工厂
                          ThreadFactory threadFactory = Executors.defaultThreadFactory();
                          // 拒绝策略:丢弃任务并抛出异常
                          RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
                  
                          // 2. 创建线程池
                          ThreadPoolExecutor executor = new ThreadPoolExecutor(
                                  corePoolSize,
                                  maximumPoolSize,
                                  keepAliveTime,
                                  unit,
                                  workQueue,
                                  threadFactory,
                                  handler
                          );
                  
                          // 3. 提交任务
                          for (int i = 1; i  {
                                      System.out.println("任务" + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");
                                      Thread.sleep(1000);
                                  });
                              } catch (Exception e) {
                                  System.out.println("任务" + taskId + " 被拒绝:" + e.getMessage());
                              }
                          }
                  
                          // 4. 关闭线程池
                          executor.shutdown();
                      }
                  }
                  

                  输出结果(当任务数超过 最大线程数+队列容量=8 时,新任务被拒绝)

                  任务1 由线程 pool-1-thread-1 执行
                  任务2 由线程 pool-1-thread-2 执行
                  任务3 由线程 pool-1-thread-3 执行
                  任务4 由线程 pool-1-thread-4 执行
                  任务5 由线程 pool-1-thread-5 执行
                  任务6 由线程 pool-1-thread-1 执行
                  任务7 由线程 pool-1-thread-2 执行
                  任务8 由线程 pool-1-thread-3 执行
                  任务9 被拒绝:Task java.util.concurrent.FutureTask@6d311334 rejected from java.util.concurrent.ThreadPoolExecutor@682a0b20[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
                  任务10 被拒绝:Task java.util.concurrent.FutureTask@3d075dc0 rejected from java.util.concurrent.ThreadPoolExecutor@682a0b20[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
                  

                  注意事项:实际开发中推荐手动创建线程池,避免使用 Executors 工具类的默认方法,防止资源耗尽。

                  1.7 实战案例:多线程实现文件批量下载

                  1.7.1 需求分析

                  实现一个多线程文件下载工具,支持从指定 URL 列表批量下载文件,要求:

                  1. 使用线程池管理下载线程,控制并发数。
                  2. 记录每个文件的下载状态(成功/失败)。
                  3. 支持断点续传(可选)。

                  1.7.2 代码实现

                  import java.io.*;
                  import java.net.HttpURLConnection;
                  import java.net.URL;
                  import java.util.ArrayList;
                  import java.util.List;
                  import java.util.concurrent.*;
                  
                  /**
                   * 多线程文件批量下载工具
                   */
                  public class MultiThreadFileDownloader {
                      // 线程池:核心线程数2,最大线程数4
                      private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
                              2,
                              4,
                              60,
                              TimeUnit.SECONDS,
                              new ArrayBlockingQueue(2),
                              Executors.defaultThreadFactory(),
                              new ThreadPoolExecutor.CallerRunsPolicy()
                      );
                  
                      // 下载状态列表
                      private static final List DOWNLOAD_STATUS = new ArrayList();
                  
                      /**
                       * 下载单个文件
                       * @param fileUrl 文件URL
                       * @param savePath 保存路径
                       */
                      private static void downloadFile(String fileUrl, String savePath) {
                          HttpURLConnection connection = null;
                          InputStream in = null;
                          OutputStream out = null;
                          try {
                              URL url = new URL(fileUrl);
                              connection = (HttpURLConnection) url.openConnection();
                              connection.setConnectTimeout(5000);
                              connection.setReadTimeout(5000);
                  
                              // 获取文件名
                              String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
                              File saveFile = new File(savePath + File.separator + fileName);
                  
                              // 读取文件流并写入本地
                              in = connection.getInputStream();
                              out = new FileOutputStream(saveFile);
                              byte[] buffer = new byte[1024 * 8];
                              int len;
                              while ((len = in.read(buffer)) != -1) {
                                  out.write(buffer, 0, len);
                              }
                  
                              String successMsg = "成功:" + fileName;
                              synchronized (DOWNLOAD_STATUS) {
                                  DOWNLOAD_STATUS.add(successMsg);
                              }
                              System.out.println(successMsg);
                          } catch (Exception e) {
                              String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
                              String failMsg = "失败:" + fileName + ",原因:" + e.getMessage();
                              synchronized (DOWNLOAD_STATUS) {
                                  DOWNLOAD_STATUS.add(failMsg);
                              }
                              System.out.println(failMsg);
                          } finally {
                              // 关闭资源
                              try {
                                  if (out != null) out.close();
                                  if (in != null) in.close();
                                  if (connection != null) connection.disconnect();
                              } catch (IOException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  
                      /**
                       * 批量下载文件
                       * @param urlList URL列表
                       * @param savePath 保存路径
                       */
                      public static void batchDownload(List urlList, String savePath) {
                          // 检查保存路径是否存在
                          File saveDir = new File(savePath);
                          if (!saveDir.exists()) {
                              saveDir.mkdirs();
                          }
                  
                          // 提交下载任务
                          for (String url : urlList) {
                              EXECUTOR.submit(() -> downloadFile(url, savePath));
                          }
                  
                          // 关闭线程池
                          EXECUTOR.shutdown();
                          try {
                              // 等待所有任务完成
                              EXECUTOR.awaitTermination(1, TimeUnit.HOURS);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                  
                          // 打印下载结果
                          System.out.println("\n===== 下载完成 ======");
                          for (String status : DOWNLOAD_STATUS) {
                              System.out.println(status);
                          }
                      }
                  
                      public static void main(String[] args) {
                          // 测试URL列表(替换为实际可下载的URL)
                          List urlList = new ArrayList();
                          urlList.add("https://example.com/file1.txt");
                          urlList.add("https://example.com/file2.jpg");
                          urlList.add("https://example.com/file3.pdf");
                          urlList.add("https://example.com/file4.zip");
                          urlList.add("https://example.com/file5.doc");
                  
                          // 批量下载到D盘download目录
                          batchDownload(urlList, "D:\\download");
                      }
                  }
                  

                  1.7.3 案例总结

                  这个多线程下载工具综合运用了线程池、同步机制、IO流等知识,核心亮点:

                  1. 使用线程池控制并发数,避免线程过多导致系统资源耗尽。
                  2. 使用 synchronized 保证下载状态列表的线程安全。
                  3. 采用 CallerRunsPolicy 拒绝策略,当任务过多时,由调用线程执行,避免任务丢失。
                  4. 完善的资源关闭和异常处理,保证程序健壮性。

                  总结

                  线程是进程的执行单元,多线程可以提升程序并发效率,充分利用 CPU 资源。

                  线程有三种创建方式:继承 Thread、实现 Runnable、实现 Callable,推荐使用后两种。

                  线程的生命周期包含六种状态,核心是掌握状态之间的转换条件。

                  并发安全问题的根源是多个线程操作共享资源,解决方案有 synchronized 同步代码块/方法、Lock 锁。

                  线程池可以复用线程,降低系统开销,实际开发中推荐手动创建 ThreadPoolExecutor。

                  多线程编程的核心是线程同步和资源管理,要注意避免死锁、内存可见性等问题。

                  以上为个人经验,希望能给大家一个参考,也希望大家多多支持爱读视界。

                  您可能感兴趣的文章:

                  • Java多线程四个案例精讲(含代码)
                  • java雪花算法在多线程环境下重复ID问题处理
                  • JAVA多线程并发编程:并发容器与线程协作实战指南
                  • Java多线程三大特性之原子性、可见性、有序性详解
                  • Java多线程编程怎么学?线程同步与线程池实战详解
                  • Java多线程与并发完整代码详解
                  读到这里,发现更多一点。探索更多内容 ↗