好的,我们来彻底讲清楚
是什么。这是理解Java线程和线程池最最基础、最核心的概念。
Runnable
一、
Runnable
的本质:一个“任务”
Runnable
忘掉那些复杂的术语。你可以把
理解成 一份“工作任务书” 或者 一个“菜谱”。
Runnable
它本身不是一个线程,它不执行任何代码。它只是一张纸,纸上写着:“要完成某某工作,你需要按以下步骤操作:1… 2… 3…”
在Java中,这张“纸”就是一个接口。
// Runnable 接口的源码,非常简单!
@FunctionalInterface
public interface Runnable {
void run(); // 它只有一个方法:run()
}
:这个方法里面定义的,就是任务的具体逻辑。比如“计算订单金额”、“发送一封邮件”、“从数据库查询用户信息”。
void run()
:这个注解意味着它是一个函数式接口,可以用 Lambda 表达式来创建,这让代码变得非常简洁。
@FunctionalInterface
二、如何创建“工作任务书” (
Runnable
)?
Runnable
你有三种主要方式来创建一份任务书:
1. 创建一个实现
Runnable
接口的类(传统方式)
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
这是最容易混淆的地方。我们回到建筑工地的比喻:
任务:就是那张**“挖坑”的图纸**。它定义了要挖多深、多宽,但它自己不会动。
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
和线程池:任务队列
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
Runnable
Callable
你可能会听到另一个类似的东西
。它们的区别很简单:
Callable
特性 |
|
|
---|---|---|
方法 |
|
|
返回值 | 没有返回值 | 有返回值 ( ) |
异常 | 不能抛出受检异常 | 可以抛出受检异常 |
用途 | 用于定义不需要返回结果的异步任务 | 用于定义需要返回结果的异步任务 |
提交方式 |
|
|
举例:
:发送邮件。发完就完了,不需要告诉我结果(或者结果通过日志等其他方式知道)。
Runnable
:计算订单总价。我交给你这个任务,你必须把计算好的总价(结果)返回给我。
Callable
总结
是一个接口,它定义了 “要执行的任务是什么” (
Runnable
方法)。它本身不是线程,它是一份被执行的代码指令集合。
run()
是真正的执行者,它需要接收一个
Thread
来知道要干什么。线程池 是一个高级的任务执行管理器,你只需要不断地向它提交
Runnable
任务,它会负责分配线程、排队、执行等所有复杂的工作。
Runnable
所以,当你看到
时,就在心里把它翻译成 “一个任务” ,一切就豁然开朗了。
Runnable
暂无评论内容