Java多线程:Callable解析(附实战案例)

Java多线程:Callable解析(附实战案例)

📌 文章提示

适合人群:具备Java基础、了解线程基本概念 你将学会:

线程创建的四种方式

Callable与Runnable的核心区别

FutureTask的实战应用技巧

避免常见多线程陷阱的方法

目录

📌 文章提示

🌟 前言

一、线程创建的四大门派

1. 继承Thread类(青铜段位)

2. 实现Runnable接口(白银段位)

3. Callable+FutureTask(黄金组合)

4. 线程池(王者之选)

二、Runnable vs Callable 世纪对决

1. Callable 接口

2. FutureTask 类

1.代码示例:

2.代码分析:

总结

三、必知必会的两大特性

1. 结果缓存机制

2. 阻塞式获取结果

🔥 高频面试题精解

💡 总结与升华

🌟 前言

在Java多线程开发中,Runnable和Callable就像两位性格迥异的双胞胎兄弟。他们都能执行异步任务,但一个沉默寡言(没有返回值),一个活泼外向(能带回结果)。今天我们将通过生活化的比喻和实战代码,带你彻底掌握这对兄弟的差异,以及他们的黄金搭档——FutureTask的使用秘籍!

一、线程创建的四大门派

1. 继承Thread类(青铜段位)

class MyThread extends Thread {

public void run() {

System.out.println("继承Thread方式");

}

}

// 使用

new MyThread().start();

2. 实现Runnable接口(白银段位)

class MyRunnable implements Runnable {

public void run() {

System.out.println("实现Runnable方式");

}

}

// 使用

new Thread(new MyRunnable()).start();

3. Callable+FutureTask(黄金组合)

class MyCallable implements Callable {

public String call() throws Exception {

return "Callable返回值";

}

}

// 使用

FutureTask task = new FutureTask<>(new MyCallable());

new Thread(task).start();

System.out.println(task.get()); // 获取返回值

4. 线程池(王者之选)

ExecutorService pool = Executors.newFixedThreadPool(2);

pool.submit(() -> System.out.println("线程池方式"));

pool.shutdown();

想要了解更多可以参考作者的: JUC多线程:一篇文章搞懂线程池:从核心参数到源码设计全解析-CSDN博客

二、Runnable vs Callable 世纪对决

在 Java 中,Callable 接口和 FutureTask 类提供了比 Runnable 更强大的功能,特别是支持返回值和异常处理

1. Callable 接口

定义:Callable 接口类似于 Runnable,但它可以返回一个结果,并且可以抛出受检异常。 方法:Callable 接口只有一个方法 call(),该方法返回一个泛型类型的结果,并且可以抛出 Exception。

2. FutureTask 类

定义:FutureTask 类实现了 Runnable 和 Future 接口,可以包装一个 Callable 对象,并提供异步执行任务的能力。 功能:

异步执行:可以将 Callable 任务提交给线程池或单独的线程执行。获取结果:通过 FutureTask 可以获取 Callable 任务的执行结果。缓存结果:一旦 Callable 任务执行完毕,结果会被缓存,后续调用 get() 方法不会重新执行任务。

1.代码示例:

package JUC.Usafe;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

/**

* 1、探究原理

* 2、觉自己会用

*/

public class CallableTest {

public static void main(String[] args) throws ExecutionException,

InterruptedException {

// new Thread(new Runnable()).start();

// new Thread(new FutureTask()).start();

// new Thread(new FutureTask( Callable )).start();

new Thread().start(); // 怎么启动Callable

MyThread thread = new MyThread();

FutureTask futureTask = new FutureTask(thread); // 适配类

new Thread(futureTask,"A").start();

new Thread(futureTask,"B").start(); // 结果会被缓存,效率高

Integer o = (Integer) futureTask.get(); //这个get 方法可能会产生阻塞!把他放到最后

// 或者使用异步通信来处理!

System.out.println(o);

}

}

class MyThread implements Callable {

@Override

public Integer call() {

System.out.println("call()"); // 会打印几个call

// 耗时的操作

return 1024;

}

}

2.代码分析:

CallableTest 类展示了如何使用 Callable 和 FutureTask 来执行任务并获取结果。以下是关键点分析: MyThread 类实现 Callable 接口:

class MyThread implements Callable {

@Override

public Integer call() {

System.out.println("call()"); // 会打印几个call

// 耗时的操作

return 1024;

}

}

MyThread 类实现了 Callable 接口,重写了 call() 方法。 call() 方法打印 "call()" 并返回整数 1024。 CallableTest 类中的 main 方法

public class CallableTest {

public static void main(String[] args) throws ExecutionException, InterruptedException {

MyThread thread = new MyThread();

FutureTask futureTask = new FutureTask<>(thread); // 适配类

new Thread(futureTask, "A").start();

new Thread(futureTask, "B").start(); // 结果会被缓存,效率高

Integer o = futureTask.get(); // 这个get 方法可能会产生阻塞!把他放到最后

// 或者使用异步通信来处理!

System.out.println(o);

}

}

创建 Callable 实例:MyThread thread = new MyThread(); 创建 FutureTask 实例:FutureTask futureTask = new FutureTask<>(thread); 启动线程: new Thread(futureTask, "A").start(); new Thread(futureTask, "B").start(); 获取结果:Integer o = futureTask.get(); 打印结果:System.out.println(o);

3.call() 方法的执行情况

线程 A 和线程 B 启动 两个线程 A 和 B 都启动并尝试执行 futureTask。 futureTask 包装了 MyThread 实例,因此两个线程都会调用 MyThread 的 call() 方法。 call() 方法的执行 第一次调用:假设线程 A 先获取到 futureTask 的锁并执行 call() 方法。 打印 "call()" 返回 1024 结果被缓存 第二次调用:线程 B 尝试执行 call() 方法,但由于结果已被缓存,call() 方法不会再次执行。 直接返回缓存的结果 1024

输出结果:

call() 方法的打印次数: 只会打印一次 "call()",因为 FutureTask 会缓存 Callable 任务的结果,后续的调用不会重新执行 call() 方法。

总结

Callable 接口:允许任务返回结果并抛出异常。FutureTask 类:包装 Callable 任务,提供异步执行和结果获取的功能。缓存机制:FutureTask 会缓存 Callable 任务的结果,避免重复执行。call() 方法:只会执行一次,结果被缓存后,后续调用不会重新执行。

特性RunnableCallable返回值❌ 无✅ 有异常处理❌ 只能try-catch✅ 可抛出方法签名void run()V call() throws适用场景简单异步任务需要结果的复杂任务

三、必知必会的两大特性

1. 结果缓存机制

FutureTask task = new FutureTask<>(() -> "结果只能获取一次");

new Thread(task).start();

System.out.println(task.get()); // 输出:结果只能获取一次

System.out.println(task.get()); // 仍然输出相同结果

// 注意:任务只会执行一次,多次get()获取的是缓存结果

2. 阻塞式获取结果

FutureTask task = new FutureTask<>(() -> {

TimeUnit.SECONDS.sleep(5);

return 42;

});

new Thread(task).start();

System.out.println("开始等待结果...");

Integer result = task.get(); // 这里会阻塞5秒

System.out.println("得到结果:" + result);

🔥 高频面试题精解

Q:为什么Callable不能直接传给Thread? A:Thread构造函数只接受Runnable类型。Callable需要FutureTask这个"适配器"来桥接,就像Type-C转接头能让USB设备连接手机一样。

Q:如何处理Callable的异常? A:两种方式:

// 方式1:在call()方法内部处理

Callable c = () -> {

try {

// 可能出错的代码

} catch (Exception e) {

return "错误处理结果";

}

};

// 方式2:通过Future.get()捕获

try {

future.get();

} catch (ExecutionException e) {

Throwable cause = e.getCause(); // 获取原始异常

}

💡 总结与升华

通过本文的学习,我们掌握了:

线程创建四大法

单线程简单任务用Thread/Runnable

需要返回值用Callable+FutureTask黄金组合

高并发场景必用线程池

Callable三大优势

能带回任务结果(就像外卖小哥送餐后带回确认签名)

支持异常传播(发现问题及时上报)

配合FutureTask实现异步回调

FutureTask使用秘籍

通过get()获取结果时记得处理阻塞

利用缓存特性避免重复计算

结合线程池使用效果更佳

终极实战建议:

耗时计算任务(如Excel导出)使用Callable

需要聚合多个服务结果时用FutureTask集合

重要业务务必处理ExecutionException

扩展思考:当我们需要同时处理100个异步任务时,如何优化?这时就该请出CompletableFuture和Fork/Join框架这些高级装备了,它们能让多线程编程如虎添翼!

相关推荐

正在阅读:如何设置电脑不休眠不锁屏 电脑屏幕设置不休眠方法【详解】如何设置电脑不休眠不锁屏 电脑屏幕设置不休眠方法【详解】
如何从零开始系统学习炒股? 很多新手朋友想炒股,对股市有兴趣,但是不知道从哪里开始做起,怎么做,做什么?雪球官方《股票投资二十四章》课程从宏观、行业...
我们吃了 10 种酱,找出和薯条最配的那一种!
格力空调制热一般多久能热起来?
日博365怎么样

格力空调制热一般多久能热起来?

📅 09-29 👁️ 1699
WLK怀旧服符文宝珠怎么获得 可以囤冰冻宝珠兑换吗
日博365怎么样

WLK怀旧服符文宝珠怎么获得 可以囤冰冻宝珠兑换吗

📅 09-14 👁️ 1715
《征服之刃》最强的下属排行
日博365怎么样

《征服之刃》最强的下属排行

📅 08-17 👁️ 8797
Tor Project
365365bet

Tor Project

📅 09-29 👁️ 6211
外籍华人如何注销中国户口-中国户籍注销流程
中国再度打破西方封锁!又一关键材料被探明,引来全球疯狂抢购,