Runnable详解

好的,我们来彻底讲清楚
Runnable
是什么。这是理解Java线程和线程池最最基础、最核心的概念。

一、
Runnable
的本质:一个“任务”

忘掉那些复杂的术语。你可以把
Runnable
理解成 一份“工作任务书” 或者 一个“菜谱”

它本身不是一个线程,它不执行任何代码。它只是一张纸,纸上写着:“要完成某某工作,你需要按以下步骤操作:1… 2… 3…

在Java中,这张“纸”就是一个接口


// Runnable 接口的源码,非常简单!
@FunctionalInterface
public interface Runnable {
    void run(); // 它只有一个方法:run()
}


void run()
:这个方法里面定义的,就是任务的具体逻辑。比如“计算订单金额”、“发送一封邮件”、“从数据库查询用户信息”。
@FunctionalInterface
:这个注解意味着它是一个函数式接口,可以用 Lambda 表达式来创建,这让代码变得非常简洁。


二、如何创建“工作任务书” (
Runnable
)?

你有三种主要方式来创建一份任务书:

1. 创建一个实现
Runnable
接口的类(传统方式)

这就像你正儿八经地写一份工作手册。


// 1. 定义一个“发送邮件的任务书”
public class SendEmailTask implements Runnable {
    private String to;
    private String content;

    public SendEmailTask(String to, String content) {
        this.to = to;
        this.content = content;
    }

    // 2. 实现run方法,里面是任务的具体步骤
    @Override
    public void run() {
        // 这里是执行任务的具体代码
        System.out.println("正在给 " + to + " 发送邮件,内容: " + content);
        // 实际开发中这里会是调用邮件发送API的代码
    }
}

// 如何使用:
Runnable emailTask = new SendEmailTask("customer@example.com", "您的订单已发货!");
2. 使用匿名内部类

这就像临时手写一张任务便签。


// 直接new一个Runnable,并当场实现它的run方法
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("这是一个临时的任务!");
    }
};
3. 使用 Lambda 表达式(最常用、最简洁的方式)

这就像是直接口述任务内容。


// 因为Runnable只有一个方法,Lambda可以完美替代
Runnable task = () -> {
    // 在箭头后面写任务的具体步骤
    System.out.println("使用Lambda创建的非常简洁的任务!");
};

三、
Runnable

Thread
的关系:任务 vs. 工人

这是最容易混淆的地方。我们回到建筑工地的比喻:


Runnable
任务
:就是那张**“挖坑”的图纸**。它定义了要挖多深、多宽,但它自己不会动。
Thread
线程
:就是真正的工人,他有手有脚,可以干活。
Thread
:是工人的管理者。你告诉这个管理者:“去把这个图纸上的活干了!

代码体现:


// 1. 创建任务(图纸)
Runnable digTask = () -> {
    System.out.println("吭哧吭哧,我在挖坑!");
};

// 2. 创建线程(工人),并把图纸交给他
Thread worker = new Thread(digTask);

// 3. 命令工人开始按照图纸干活!
worker.start(); // start()方法会启动新线程,在新线程中执行digTask.run()

关键点:

一个
Runnable
任务(图纸)可以交给多个
Thread
工人去执行(虽然通常不这么做)。一个
Thread
工人一次只能执行一个
Runnable
任务。调用
worker.start()
后,会创建一个新的执行路径(新线程),工人会在这个新路径上异步地执行
run()
方法里的代码。这意味着主线程(你自己)不会等待他干完活,可以继续做其他事。


四、
Runnable
和线程池:任务队列

现在我们把线程池加进来。线程池就是一个现代化的建筑公司

你(生产者):你不再直接雇佣工人 (
Thread
),你只负责生产任务图纸 (
Runnable
)。线程池(公司):公司管理着一批工人 (
Worker Threads
),还有一个任务队列 (
BlockingQueue<Runnable>
)。工作流程
你把图纸 (
Runnable
) 交给公司 (
executor.execute(runnable)
)。公司的前台收到图纸,如果发现有空闲工人,立刻把图纸给他去干。如果工人都忙着了,前台就把图纸放到一个“待办任务筐”(队列)里排队。任何一个工人干完手上的活,就会立刻去“待办任务筐”里拿下一张图纸继续干。

代码体现:


// 1. 创建一个线程池(建筑公司),公司规定核心员工4人,最大员工10人,任务队列最多堆100张图纸。
ExecutorService executor = Executors.newFixedThreadPool(4);

// 2. 创建并提交5个任务(画5张图纸)
for (int i = 0; i < 5; i++) {
    final int taskId = i;
    Runnable task = () -> { // 定义任务内容
        System.out.println("线程: " + Thread.currentThread().getName() + " 正在执行任务: " + taskId);
    };
    executor.execute(task); // 3. 把图纸交给公司!
}

// 4. 告诉公司不再接收新图纸了,但要把已经提交的图纸都干完。
executor.shutdown();

输出可能类似于:


线程: pool-1-thread-1 正在执行任务: 0
线程: pool-1-thread-2 正在执行任务: 1
线程: pool-1-thread-3 正在执行任务: 2
线程: pool-1-thread-4 正在执行任务: 3
线程: pool-1-thread-1 正在执行任务: 4 // thread-1干完任务0后,从队列拿到了任务4

五、
Runnable
vs.
Callable

你可能会听到另一个类似的东西
Callable
。它们的区别很简单:

特性
Runnable

Callable
方法
void run()

V call() throws Exception
返回值 没有返回值 返回值 (
V
)
异常 不能抛出受检异常 可以抛出受检异常
用途 用于定义不需要返回结果的异步任务 用于定义需要返回结果的异步任务
提交方式
execute(Runnable)

submit(Callable)

举例:


Runnable
发送邮件。发完就完了,不需要告诉我结果(或者结果通过日志等其他方式知道)。
Callable
计算订单总价。我交给你这个任务,你必须把计算好的总价(结果)返回给我。

总结


Runnable
是一个接口,它定义了 “要执行的任务是什么” (
run()
方法)。它本身不是线程,它是一份被执行的代码指令集合
Thread
真正的执行者,它需要接收一个
Runnable
来知道要干什么。线程池 是一个高级的任务执行管理器,你只需要不断地向它提交
Runnable
任务,它会负责分配线程、排队、执行等所有复杂的工作。

所以,当你看到
Runnable
时,就在心里把它翻译成 “一个任务” ,一切就豁然开朗了。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
小欲同学的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容