Java 多线程创建方式

一、先搞懂核心基础

Java 中,线程的本质是Thread类的实例。所有创建线程的方式,最终都是通过Thread类来启动线程,区别在于任务逻辑的定义方式不同。

二、方式 1:继承 Thread 类

1. 核心原理

Thread类本身实现了Runnable接口,代表一个线程实例。

  • 继承Thread类,重写run()方法,把线程要执行的任务写在run()
  • 调用start()方法启动线程(注意:不能直接调用 run (),否则只是普通方法调用,不会启动新线程

2. 代码实现

java 复制代码
// 1. 自定义类继承Thread
class MyThread extends Thread {
    // 2. 重写run()方法,定义线程任务
    @Override
    public void run() {
        // 线程执行的业务逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行:" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        // 3. 创建线程对象
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        // 4. 调用start()启动线程(JVM会自动调用run())
        thread1.setName("线程A");
        thread2.setName("线程B");
        thread1.start();
        thread2.start();
    }
}

3. 关键细节

  • start()的作用:通知 JVM 将该线程放入就绪队列,等待 CPU 调度 ,调度后自动执行run()
  • run()的本质:普通方法,直接调用只会在当前线程(主线程)执行,不会开启新线程
  • Thread.currentThread().getName():获取当前执行代码的线程名称

4. 优缺点 & 适用场景

优点 缺点 适用场景
代码简单,直接继承即可 Java 是单继承,继承 Thread 后无法再继承其他类,扩展性差 简单的、无继承需求的线程任务

三、方式 2:实现 Runnable 接口

1. 核心原理

Runnable是一个函数式接口,只有一个run()抽象方法,仅代表 "可执行的任务",不代表线程本身

  • 实现Runnable接口,重写run()方法定义任务
  • 创建Runnable实现类对象,作为参数传入Thread类的构造器,创建线程实例
  • 调用start()启动线程

2. 代码实现

java 复制代码
// 1. 实现Runnable接口
class MyRunnable implements Runnable {
    // 2. 重写run()方法,定义任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行:" + i);
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        // 3. 创建任务对象
        MyRunnable task = new MyRunnable();

        // 4. 将任务传入Thread构造器,创建线程
        Thread thread1 = new Thread(task, "线程A");
        Thread thread2 = new Thread(task, "线程B");

        // 5. 启动线程
        thread1.start();
        thread2.start();

        //  Lambda表达式简化(函数式接口特性)
        Thread lambdaThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " Lambda线程执行");
        }, "Lambda线程");
        lambdaThread.start();
    }
}

3. 关键细节

  • 任务与线程分离 :同一个Runnable任务可以被多个线程共享,适合多线程处理同一资源的场景(如售票系统)
  • 避免了单继承限制,实现类可以继承其他类,扩展性更强
  • 是 Java 中最常用、最推荐的基础创建方式

4. 优缺点 & 适用场景

优点 缺点 适用场景
避免单继承限制,任务与线程解耦,可复用任务 无返回值,无法抛出 checked 异常 绝大多数无返回值的线程任务,是日常开发的首选

四、方式 3:实现 Callable 接口

1. 核心原理

Callable是 JDK 1.5 引入的接口,和Runnable的核心区别:

  • Callablecall()方法有返回值,可以抛出 checked 异常
  • Runnablerun()方法无返回值,不能抛出 checked 异常
  • Callable需要配合FutureTask(实现了RunnableFuture接口,间接实现Runnable)使用,才能作为Thread的任务

2. 代码实现

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 1. 实现Callable接口,泛型指定返回值类型
class MyCallable implements Callable<Integer> {
    // 2. 重写call()方法,定义任务,有返回值
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + " 累加:" + i);
        }
        return sum; // 返回计算结果
    }
}

public class CallableDemo {
    public static void main(String[] args) throws Exception {
        // 3. 创建Callable任务
        MyCallable callableTask = new MyCallable();

        // 4. 包装成FutureTask(FutureTask实现了Runnable,可传入Thread)
        FutureTask<Integer> futureTask = new FutureTask<>(callableTask);

        // 5. 创建线程,启动
        Thread thread = new Thread(futureTask, "累加线程");
        thread.start();

        // 6. 获取线程执行结果(get()会阻塞当前线程,直到任务执行完成)
        Integer result = futureTask.get();
        System.out.println("线程执行结果:" + result);
    }
}

3. 关键细节

  • FutureTask的作用:桥梁 ,将Callable适配为Runnable,同时提供了获取结果、取消任务、判断状态的方法
  • get()方法:阻塞方法,会等待线程执行完成后返回结果,若任务未完成,主线程会一直阻塞
  • cancel(boolean mayInterruptIfRunning):取消任务,true表示中断正在执行的任务
  • isDone():判断任务是否执行完成

4. 优缺点 & 适用场景

表格

优点 缺点 适用场景
有返回值,可抛出异常,适合需要结果的异步任务 代码相对复杂,需要配合 FutureTask 异步计算、需要获取线程执行结果的场景(如大数据计算、接口

额外拓展 --- 为什么 CallableRunnable 多了FutureTask包装

一、接口兼容性("翻译" 作用)

我们看一下 Java 的类继承结构:

  1. Thread :只能接收 Runnable 类型的任务。
    • 构造函数:Thread(Runnable target)
  2. Callable 接口 :是一个独立的接口,并没有继承 Runnable
    • 它的方法是 V call() throws Exception,有返回值和异常。

矛盾点 :你不能直接把一个 Callable 对象丢给 Thread,因为 Thread 构造器只认 Runnable

解决方案FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 同时继承了 RunnableFuture

  • 它实现了 Runnable可以传给 Thread,骗过了 Thread 的检查。
  • 它持有了 Callable 对象:内部保存了你的具体逻辑

二、 内部原理:它是怎么工作的?

FutureTask 的内部逻辑非常巧妙,它完成了两件事:

1. 承接任务(适配)
java 复制代码
public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable; // 内部持有Callable任务
    
    // 构造器
    public FutureTask(Callable<V> callable) {
        this.callable = callable; // 把传进来的Callable存起来
    }
}
2. 执行逻辑(桥接)

Thread 启动后,会调用 FutureTaskrun() 方法。FutureTaskrun() 方法内部会主动调用 你传入的 Callable.call()

java 复制代码
// FutureTask.run() 的伪代码逻辑
public void run() {
    // 调用实际的Callable任务
    V result = callable.call(); 
    // 把结果存起来
    set(result); 
}

三、 它的核心价值:不仅是 "桥梁",更是 "管家"

如果只是为了把 Callable 变成 Runnable,那只需要一个简单的包装类就行。但 FutureTask 之所以叫 Future(未来),是因为它还提供了异步获取结果的能力。

它额外提供了三大核心功能(这也是为什么不能直接用 Thread 执行 Callable 的原因):

1. 结果存储 (get)

Thread 执行完 run() 方法就结束了,它没有地方存返回值。FutureTask 内部有一个变量 private Object outcome;,专门用来存储 call() 的返回结果。

2. 阻塞等待 (get ())

这是最关键的功能。

  • 如果你直接 new Thread(new MyCallable()).start();,线程执行完就结束了,你永远拿不到计算结果
  • 有了 FutureTask,你可以调用 futureTask.get()
    • 原理 :如果任务还没执行完,get() 方法会阻塞主线程,直到任务执行完毕并把结果拿回来。
    • 作用:保证了 "同步等待结果" 的逻辑。
3. 状态管理

FutureTask 内部维护了任务的状态(NEW -> COMPLETING -> NORMAL 等)。

  • 它能知道任务是正常结束抛出异常 还是被取消了

四、 总结

为什么 CallableRunnable 多了这个包装?

  1. 为了合规Thread 只认 Runnable,必须通过 FutureTask 中转。
  2. 为了功能 :单纯的 Runnable 运行完就消失了,什么都留不下。
  3. 为了异步FutureTask 赋予了任务 "存结果、等结果、查状态" 的能力,实现了真正的异步计算

五、方式 4:线程池(生产环境核心方案)

前面 3 种方式都是手动创建销毁线程 ,线程的创建和销毁会消耗大量系统资源,高并发场景下性能极差。线程池的核心思想:提前创建好一批线程,放入池中复用,任务提交后直接分配线程执行,执行完线程不销毁,等待下一个任务,避免频繁创建销毁,提升性能。

分为两类:Executors工厂类、ThreadPoolExecutor自定义线程池


4.1 Executors 工厂类(快速创建线程池,不推荐生产环境使用)

Executors是 JDK 提供的线程池工具类,封装了 4 种常用线程池,一行代码即可创建:

1. 常用 4 种线程池
方法名 核心特点 适用场景
Executors.newFixedThreadPool(int nThreads) 固定大小的线程池,核心线程数 = 最大线程数,无界队列 负载稳定的固定并发任务
Executors.newCachedThreadPool() 可缓存线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE,空闲线程 60 秒后销毁 短期大量异步任务、负载波动大的场景
Executors.newSingleThreadExecutor() 单线程线程池,保证任务按顺序执行 需要串行执行任务的场景
Executors.newScheduledThreadPool(int corePoolSize) 定时任务线程池,支持定时、周期性任务 定时任务、周期任务
2. 代码示例(newFixedThreadPool)
java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo {
    public static void main(String[] args) {
        // 1. 创建固定大小为3的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        // 2. 提交任务(可以是Runnable或Callable)
        for (int i = 0; i < 10; i++) {
            int taskNum = i;
            threadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskNum);
            });
        }

        // 3. 关闭线程池(不再接受新任务,等待现有任务执行完)
        threadPool.shutdown();
    }
}
3. 为什么不推荐生产环境使用?

Executors创建的线程池默认使用无界队列LinkedBlockingQueue,当任务量暴增时,队列会无限堆积,最终导致 OOM(内存溢出),违反阿里 Java 开发手册规范。


4.2 ThreadPoolExecutor 自定义线程池(生产环境标准方案)

ThreadPoolExecutor是线程池的底层实现类 ,所有Executors的线程池都是基于它封装的。自定义线程池可以精准控制线程池的所有参数,避免 OOM 风险。

1. 核心构造参数(7 大参数)
java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,         // 核心线程数:线程池长期保留的线程数,即使空闲也不会销毁
    int maximumPoolSize,      // 最大线程数:线程池能创建的最大线程数
    long keepAliveTime,       // 空闲线程存活时间:非核心线程空闲超过该时间会被销毁
    TimeUnit unit,            // 时间单位:keepAliveTime的单位(秒、毫秒等)
    BlockingQueue<Runnable> workQueue, // 任务队列:存放等待执行的任务的阻塞队列
    ThreadFactory threadFactory,       // 线程工厂:创建线程的工厂,可自定义线程名、优先级等
    RejectedExecutionHandler handler   // 拒绝策略:当队列满、线程数达到最大值时,对新任务的处理策略
) {}
2. 关键参数详解
  • 核心线程数 corePoolSize:根据业务压测结果设置,一般设置为 CPU 核心数 * 2(IO 密集型)或 CPU 核心数(CPU 密集型)
  • 任务队列 workQueue
    • ArrayBlockingQueue:有界队列,必须指定大小,生产环境推荐,避免 OOM
    • LinkedBlockingQueue:无界队列,Executors 默认使用,不推荐
    • SynchronousQueue:不存储任务,直接提交给线程,适合高并发、任务处理快的场景
  • 拒绝策略 handler (4 种内置策略):
    1. AbortPolicy(默认):直接抛出RejectedExecutionException,阻止系统运行
    2. CallerRunsPolicy:由提交任务的线程(主线程)执行该任务,不会丢弃
    3. DiscardPolicy:直接丢弃新任务,不抛异常
    4. DiscardOldestPolicy:丢弃队列中最老的任务,执行新任务生产环境推荐自定义拒绝策略,记录日志、告警、降级处理
3. 代码实现(生产环境标准自定义线程池)
java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 自定义线程工厂(给线程起有意义的名字,方便排查问题)
        ThreadFactory namedThreadFactory = new ThreadFactory() {
            private final AtomicInteger threadNum = new AtomicInteger(1);
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "custom-pool-thread-" + threadNum.getAndIncrement());
                thread.setPriority(Thread.NORM_PRIORITY); // 设置线程优先级
                return thread;
            }
        };

        // 2. 自定义拒绝策略(记录日志+告警)
        RejectedExecutionHandler customHandler = (r, executor) -> {
            System.err.println("线程池已满,拒绝任务:" + r.toString() + ",当前队列大小:" + executor.getQueue().size());
            // 实际生产中可接入告警系统、消息队列降级
        };

        // 3. 创建自定义线程池(核心参数根据业务调整)
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                5,                          // 核心线程数5
                10,                         // 最大线程数10
                60L,                        // 空闲线程存活60秒
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100), // 有界队列,大小100,避免OOM
                namedThreadFactory,          // 自定义线程工厂
                customHandler                // 自定义拒绝策略
        );

        // 4. 提交任务
        for (int i = 0; i < 20; i++) {
            int taskNum = i;
            threadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskNum);
                try {
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 5. 关闭线程池
        threadPool.shutdown();
    }
}
4. 线程池工作流程(核心原理)
  1. 提交任务时,若核心线程数未满,创建核心线程执行任务
  2. 若核心线程数已满,任务进入阻塞队列等待
  3. 若队列已满,创建非核心线程执行任务(直到达到最大线程数)
  4. 若线程数达到最大值,触发拒绝策略处理新任务
  5. 非核心线程空闲超过keepAliveTime后,被销毁,最终线程池保留核心线程数
5. 优缺点 & 适用场景

表格

优点 缺点 适用场景
线程复用,避免频繁创建销毁,提升性能;可精准控制并发数,防止 OOM 代码相对复杂,需要合理设置参数 生产环境所有高并发场景,是 Java 多线程的标准解决方案

避坑指南

1. start () 和 run () 的区别?

  • start():启动线程,JVM 将线程放入就绪队列,等待 CPU 调度,真正开启新线程
  • run():普通方法,直接调用只会在当前线程执行,不会开启新线程

2. Runnable 和 Callable 的区别?

  • Runnablerun()无返回值,不能抛 checked 异常
  • Callablecall()有返回值,可抛 checked 异常,需要配合FutureTask使用

3. 线程池的 7 大参数是什么?工作流程?

见上文 4.2 部分

4. 为什么不推荐使用 Executors 创建线程池?

  • newFixedThreadPool/newSingleThreadExecutor:使用无界队列LinkedBlockingQueue,任务堆积导致 OOM
  • newCachedThreadPool/newScheduledThreadPool:最大线程数为Integer.MAX_VALUE,可创建无限多线程,导致 OOM

5. 线程池的拒绝策略有哪些?

见上文 4.2 部分,4 种内置策略 + 自定义策略


八、补充:Lambda 表达式简化创建

Java 8 之后,RunnableCallable都是函数式接口,可以用 Lambda 表达式大幅简化代码:

java 复制代码
// Runnable简化
new Thread(() -> System.out.println("Lambda线程执行")).start();

// Callable简化
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
    return 100; // 直接写call()逻辑
});
new Thread(futureTask).start();
相关推荐
科雷软件测试7 小时前
Python中itertools.product:快速生成笛卡尔积
开发语言·python
OOJO8 小时前
c++---list介绍
c语言·开发语言·数据结构·c++·算法·list
笨笨饿10 小时前
29_Z变换在工程中的实际意义
c语言·开发语言·人工智能·单片机·mcu·算法·机器人
艾为电子10 小时前
【技术帖】让接口不再短命:艾为 C-Shielding™ Type-C智能水汽防护技术解析
c语言·开发语言
棉花骑士11 小时前
【AI Agent】面向 Java 工程师的Claude Code Harness 学习指南
java·开发语言
IGAn CTOU11 小时前
PHP使用Redis实战实录2:Redis扩展方法和PHP连接Redis的多种方案
开发语言·redis·php
爱敲代码的小鱼11 小时前
springboot(2)从基础到项目创建:
java·spring boot·spring
环黄金线HHJX.11 小时前
TSE框架配置与部署详解
开发语言·python
Vfw3VsDKo11 小时前
Maui 实践:Go 接口以类型之名,给 runtime 传递方法参数
开发语言·后端·golang