Java 多线程基础:Thread 和 Runnable 到底有何不同?

在 Java 多线程开发中,Thread 和 Runnable 是最常被初学者混淆的两个接口/类。它们都能启动新线程,但设计哲学与使用方式大不相同。本文从源码、实践与设计角度,系统剖析两者的区别与选型建议。


一、Runnable 是接口,Thread 是类

  • Runnable 是一个函数式接口,仅定义了一个方法 run()。

  • Thread 是继承自 Object 的类,并实现了 Runnable 接口。

@FunctionalInterface
public interface Runnable {
    void run();
}

public class Thread implements Runnable {
    private Runnable target;
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

    ✅ 本质:Thread 是对 Runnable 的包装。


    二、使用方式对比:继承 vs 实现

    方式一:继承 Thread 类

    public class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread 方式运行");
        }
    }
    new MyThread().start();

    方式二:实现 Runnable 接口

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable 方式运行");
        }
    }
    Thread thread = new Thread(new MyRunnable());
    thread.start();

    推荐使用 Runnable 实现方式,更灵活、可复用。


    三、线程启动原理:Thread 与 Runnable 的交互

    Thread 构造函数接收一个 Runnable 实例并将其赋值给 target:

    public Thread(Runnable target) {
        this.target = target;
    }

    当你调用 thread.start() 后,JVM 会启动新线程执行 Thread.run() 方法。其内部判断是否有 target,如果有则调用其 run():

    @Override
    public void run() {
        if (target != null) {
            target.run();
        } else {
            // 执行自身的 run() 逻辑
        }
    }

    ✅ 结论:Thread 只是线程的载体,真正的任务由 Runnable 描述。


    四、设计哲学:职责分离与组合优雅

    • 继承 Thread:将线程行为与业务逻辑耦合,不利于继承其他类(Java 不支持多继承)。

    • 实现 Runnable:职责分离,符合组合优于继承的设计原则。

    推荐模式:

    Runnable task = () -> System.out.println("异步任务");
    new Thread(task).start();


    五、实战建议与最佳实践

    维度

    Thread

    Runnable

    可扩展性

    差(继承限制)

    高(可自由组合)

    代码复用

    与线程池集成

    不可直接使用

    完美适配

    实现复杂任务

    不推荐

    推荐

    函数式支持

    不支持

    支持 Lambda

    ✅ 实际开发中,应首选 Runnable 接口或 Callable(带返回值)配合线程池使用。


    六、拓展话题:从 Runnable 到线程池

    Runnable 是构建线程池的基础:

    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.submit(() -> {
        // 可配置任务逻辑
    });

    在 Java 8+ 中,推荐配合 CompletableFuture 与 Lambda 简化异步任务管理。


    七、Callable接口与Future的配套使用

    背景:

    Runnable 无法获取线程执行结果,也无法抛出受检异常。为此,Java 引入了 Callable<V> 与 Future<V>。

    核心原理:

    • Callable 是一个带返回值的任务定义接口。

    • Future 是一个异步计算结果的持有者,通过线程池提交任务时获取。

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<String> task = () -> {
        Thread.sleep(1000);
        return "任务完成";
    };
    Future<String> future = executor.submit(task);
    // 阻塞等待结果
    System.out.println(future.get()); 
    executor.shutdown();

    适用场景:

    • 异步任务需要结果;

    • 支持异常处理;

    • 批量并发计算(如线程并行计算合并结果)。


    八、ThreadPoolExecutor的核心线程模型

    核心结构图:

    提交任务 → 工作队列(BlockingQueue) → 核心线程(corePoolSize) → 最大线程(maximumPoolSize) → 拒绝策略

    基本构造:

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2, 4, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(100),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
    );

    ✅ 参数说明:

    • corePoolSize:核心线程数,始终保留;

    • maximumPoolSize:最大线程数;

    • keepAliveTime:非核心线程空闲超时时间;

    • workQueue:阻塞队列,缓冲提交任务;

    • handler:拒绝策略。

    ✅ 推荐配置建议:

    // CPU 密集型
    new ThreadPoolExecutor(nThreads, nThreads, ...);
    // IO 密集型
    new ThreadPoolExecutor(nThreads * 2, nThreads * 4, ...);


    九、synchronized与Lock的使用差异

    关键区别:

    特性

    synchronized

    ReentrantLock

    代码级别

    JVM 关键字

    Java 类

    可中断性

    不支持

    支持 lockInterruptibly

    公平锁

    不支持

    支持

    尝试获取(超时)

    不支持

    tryLock 支持

    条件队列

    不支持

    支持 Condition

    示例对比:

    // synchronized
    synchronized(this) {
        // 临界区
    }
    // ReentrantLock
    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();
    }

    使用建议:

    • 简单同步:优先使用 synchronized;

    • 可中断/复杂并发控制:使用 ReentrantLock。


    十、Java 21 的虚拟线程(VirtualThread)机制

    概念:

    虚拟线程是 JDK 21 正式引入的轻量级线程,由用户态调度器(ForkJoinPool)管理,旨在大幅提升高并发任务处理能力。

    创建方式:

    Runnable task = () -> System.out.println("虚拟线程执行");
    Thread.startVirtualThread(task);

    核心优势:

    • 每个任务一个线程(无需池化);

    • 非阻塞同步(适配结构化并发);

    • 避免线程资源耗尽问题。

    应用场景:

    • 高并发 IO 服务;

    • Web 服务(Tomcat / Spring MVC 未来支持);

    • 替代传统线程池设计。


    十一、ForkJoinPool 与并行流的工作窃取原理

    核心概念:

    ForkJoinPool 是 Java 并发框架的高性能线程池,采用工作窃取算法(Work Stealing),适用于任务拆分与并行执行。

    工作流程:

    • 拆分任务(fork)成子任务;

    • 子任务执行并将结果合并(join);

    • 空闲线程可从其他线程的任务队列中“窃取”任务执行。

    示例:

    ForkJoinPool pool = new ForkJoinPool();
    RecursiveTask<Integer> task = new RecursiveTask<>() {
        protected Integer compute() {
            if (/* 小任务 */) return result;
            invokeAll(subtask1, subtask2);
            return subtask1.join() + subtask2.join();
        }
    };
    int result = pool.invoke(task);

    并行流底层:

    List<Integer> list = IntStream.range(1, 1000).boxed().collect(Collectors.toList());
    int sum = list.parallelStream().mapToInt(i -> i).sum();

    底层即由 ForkJoinPool.commonPool() 支持。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    小健学 Java

    你的鼓励是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值