2051 字
10 分钟
Java---Thread
2025-02-20

Thread#

定义#

Thread 是Java对线程的直接抽象,每个线程代表独立的执行流程。JVM启动时默认会创建多个系统线程(如main线程GC线程等)

创建方式#

  • 继承Thread:重写run()方法,直接调用start()启动线程(适用于简单场景,但受限于单继承)
  • 实现Runnable接口:将任务与线程分离,通过Thread构造器传递Runnable实例,支持资源共享(推荐,灵活且避免继承限制)
  • CallableFutureTask:支持返回结果和异常处理,通过FutureTask包装Callable任务,再传递给Thread执行。

实现Runnable vs 继承Thread#

设计架构与灵活性#

  • 继承Thread:由于Java不支持多继承,若子类继承了Thread,则无法再继承其他类
  • 实现Runnable接口:接口允许多实现,任务类可以继承其他父类,代码扩展性更强

任务与线程的分离#

  • Runnable仅定义任务逻辑(通过run()方法),与线程管理解耦,便于复用任务
  • Thread将任务与线程生命周期绑定,导致代码耦合度高

资源共享能力#

  • Runnable:多个线程可共享同一Runnable实例,天然支持资源共享(如售票系统共用票数变量)
  • Thread:每个线程需创建独立的Thread实例,共享资源需额外同步机制(如静态变量),易引发竞态条件

并发工具兼容性#

  • Runnable:与Java并发工具(如Executor框架、Future)无缝集成,支持异步任务调度
  • Thread:需手动管理线程生命周期,难以适配高级并发模型

代码复用性#

  • Runnable:任务逻辑独立于线程,同一任务可被不同线程或线程池执行,提升代码复用
  • Thread:任务与线程绑定,复用性差

调试与维护#

  • Runnable:任务逻辑集中,便于单元测试和维护
  • Thread:线程管理与业务逻辑混杂,调试复杂度高

样例#

实现Runnable接口#

// 实现 Runnable 接口,通过构造函数传参
public class MyRunnable implements Runnable {
    private String param1;  // 线程参数1
    private int param2;     // 线程参数2

    // 构造函数接收参数
    public MyRunnable(String param1, int param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    @Override
    public void run() {
        System.out.println("Runnable线程执行,参数1=" + param1 + ",参数2=" + param2);
    }

    public static void main(String[] args) {
        // 创建 Runnable 实例并传递参数
        MyRunnable task = new MyRunnable("Hello", 100);
        // 通过 Thread 启动线程
        Thread thread = new Thread(task);
        thread.start();
    }
}

说明#

  • 参数传递:通过构造函数将参数注入 MyRunnable 类的成员变量,线程启动后 run() 方法直接使用这些参数

  • 优势:支持多参数传递,且线程逻辑与参数完全解耦,可复用性强

  • Lambda 简化版(适用于简单场景)

    // 直接通过 Lambda 传递参数
    new Thread(() -> System.out.println("参数=" + 42)).start();

继承Thread类#

// 继承 Thread 类,通过构造函数传参
public class MyThread extends Thread {
    private String param1;
    private int param2;

    // 构造函数接收参数
    public MyThread(String param1, int param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    @Override
    public void run() {
        System.out.println("Thread线程执行,参数1=" + param1 + ",参数2=" + param2);
    }

    public static void main(String[] args) {
        // 直接创建子类实例并启动线程
        MyThread thread = new MyThread("World", 200);
        thread.start();
    }
}

说明#

  • 参数传递:通过子类构造函数直接初始化参数,run() 方法调用时直接使用
  • 限制:每个线程必须单独创建 Thread 子类实例,无法共享任务对象,导致资源利用率较低
  • 注意点:避免直接调用 run() 方法(会导致同步执行而非启动新线程)

核心属性和方法#

属性#

  • ID:唯一标识符 (getId()
  • 名称:调试用(getName()
  • 状态:线程生命周期状态(如NEWRUNNABLE等)
  • 优先级:1-10级(默认5,对调度影响有限)
  • 前台/后台线程
    • 前台线程阻止JVM退出(默认),后台线程(守护线程)不会(setDaemon(true))。

关键方法#

  • start():启动线程,触发run()执行
  • sleep():阻塞当前线程,不释放锁
  • join():等待线程终止
  • interrupt():中断线程(需配合状态检查)

线程安全与资源管理#

  • 资源共享:多个线程访问共享变量需同步(如synchronizedLock
  • 使用场景
    • Thread:适合无需共享资源、简单任务的场景
    • Runnable:需资源共享、代码复用或避免继承限制时优先选择

ThreadLocal(线程本地变量)#

作用#

  • 数据隔离:为每个线程提供独立的变量副本,避免多线程竞争(如数据库连接、用户会话等场景)

  • 示例:

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("data");  // 当前线程独有
    String data = threadLocal.get();

实现原理(底层原理)#

ThreadLocalMap

  • 每个Thread内部维护一个ThreadLocalMap,以ThreadLocal对象为键(弱引用),存储线程专属的值(强引用)
  • set/get流程
    • set():将键值对存入当前线程的ThreadLocalMap
    • get():从当前线程的Map中查找值,若未初始化则调用initialValue()

内存泄漏风险与解决方案#

  • 风险来源

    在线程池中使用ThreadLocal会造成内存泄漏,ThreadLocal对象被回收后,Map中的键(弱引用)变为null,但值仍被强引用,导致无法回收

  • 规避措施

    • 显式调用remove():使用完ThreadLocal后手动清理条目

    • 配合try-finallyAutoCloseable:确保资源释放(如连接池场景)

      try {
          threadLocal.set(value);
          // 业务逻辑
      } finally {
          threadLocal.remove();
      }

应用场景#

  • 线程私有资源管理:如数据库连接、用户身份信息

  • 避免参数传递:跨方法调用时隐式传递上下文(如SpringRequestContextHolder

Executor#

定义#

Executors是Java并发包(java.util.concurrent)中提供的线程池工厂工具类,用于简化线程池的创建和管理。它通过静态工厂方法生成不同类型的线程池实例(ExecutorService接口实现类),开发者无需手动配置核心参数即可快速使用线程池。

核心功能#

  • 创建线程池:提供多种预定义线程池类型(如固定大小、缓存型、单线程池等)
  • 任务调度:支持定时/周期性任务(通过ScheduledExecutorService实现)
  • 线程复用:通过线程池复用线程资源,避免频繁创建/销毁线程的开销

常用方法及场景#

方法名线程池类型特点适用场景
newFixedThreadPool(n)固定大小线程池核心线程数=最大线程数,队列无界(LinkedBlockingQueue负载稳定、任务量可控的场景
newCachedThreadPool()缓存型线程池线程数无限(60秒闲置回收),任务直接执行无队列短生命周期、高并发短期任务
newSingleThreadExecutor()单线程池仅1个线程顺序执行任务,队列无界需顺序执行任务的场景
newScheduledThreadPool(n)定时线程池支持延迟/周期性任务,核心线程数固定定时任务、心跳检测等场景

使用步骤(以newFixedThreadPool为例)#

// 1. 创建线程池(固定3个线程)
ExecutorService executor = Executors.newFixedThreadPool(3);

// 2. 提交任务(支持Runnable/Callable)
executor.execute(() -> {
    System.out.println("Task executed by " + Thread.currentThread().getName());
});

// 3. 关闭线程池(优雅终止)
executor.shutdown();
// 可选:等待所有任务完成(超时60秒)
executor.awaitTermination(60, TimeUnit.SECONDS);

优缺点分析#

优点#

  1. 简化开发 无需手动配置核心参数(如核心线程数、队列类型),一行代码即可创建线程池 示例:Executors.newCachedThreadPool()自动处理线程回收和任务调度
  2. 性能优化 线程复用减少资源消耗(如newFixedThreadPool复用固定线程) 缓存型线程池(newCachedThreadPool)动态扩缩容,适合处理突发流量
  3. 功能丰富 支持延迟任务(schedule())和周期性任务(scheduleAtFixedRate()

缺点及风险#

  1. 潜在OOM风险 无界队列(如FixedThreadPool使用LinkedBlockingQueue)可能导致任务堆积,内存溢出 缓存线程池(newCachedThreadPool)无最大线程数限制,可能创建过多线程耗尽资源
  2. 灵活性不足 无法自定义拒绝策略或队列类型(默认使用AbortPolicy和LinkedBlockingQueue) 例如:无法直接配置有界队列或自定义任务丢弃策略
  3. 隐藏底层细节 实际参数配置不透明(如newSingleThreadExecutor的队列容量为Integer.MAX_VALUE),调试困难
Java---Thread
https://fuwari.vercel.app/posts/javathread/
作者
Lettle
发布于
2025-02-20
许可协议
CC BY-NC-SA 4.0