面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?

简介: 对一个变量n,初始化为0,我们使用实现Runnable接口的方式创建一个线程来对其进行一次n++操作,看看能得到我们预期的结果吗?

🍊为何要使用Callable来创建线程?

对一个变量n,初始化为0,我们使用实现Runnable接口的方式创建一个线程来对其进行一次n++操作,看看能得到我们预期的结果吗?

public class MyCallable {
    private static int n;
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                n++;
            }
        });
        t1.start();
        System.out.println(n);
    }
}

👁‍🗨️结果:

微信图片_20221029150846.jpg


😮通过结果发现,没有输出我们预期的1,这是因为main线程和t1线程是并发执行的,n在什么时候修改不清楚


我们使用线程通信的方式对上述代码进行改造来达到我们预期的结果

public class MyCallable {
    private static int n;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (MyCallable.class){
                    n++;
                    MyCallable.class.notify();
                }
            }
        });
        t1.start();
        synchronized (MyCallable.class){
            while(n == 0){
                MyCallable.class.wait();
            }
            System.out.println(n);
        }
    }
}


👁️结果:可以看到,结果符合我们预期的结果



❗❗❗但是使用这种方式来达到我们预期结果,使用到了加锁释放锁,线程通信一系列操作,比较繁琐,所以我们需要使用Callable接口创建线程的方式来返回线程执行的结果


🍉Callable的使用方式

🍀创建一个Callable(泛型)对象 ,重写带返回值的call方法

🍀创建一个FutureTask任务对象task,参数传入创建的Callable对象

🍀使用Thread创建线程,参数传入task对象

🍀返回值为task.get(),当前线程阻塞等待task执行完毕并返回结果后,再执行当前线程后续任务


🍵关于Callable:


🔌Callable和Runnable都是描述一个任务,Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务

🔌Callable重写call方法,Runnable重写run方法

🔌Callable搭配FutureTask来使用,FutuerTask用来保存Callable的返回结果,因为Callable往往是在另一个线程中执行的,啥时候执行完并不清楚,所以需要使用FutuerTask来保存执行返回结果


🍋Callable的使用实例

示例一:先对上述执行一次n++的操作代码使用Callable进行改造

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable {
    private static int n;
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                n++;
                return n;
            }
        };
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread t = new Thread(task);
        t.start();
        Integer ret = task.get(); //task.get()会让main线程等待,等待t线程执行完并获取返回结果后再继续执行main线程后续代码
        System.out.println(ret);
    }
}


👁️执行结果:符合我们预期的结果

微信图片_20221029150920.jpg


示例二:我们创建线程执行1+2+3+...+50的操作并获取到结果,来进一步理解Callable的用法


❗❗❗结合注释理解

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception { //重写call方法
                int sum = 0;
                for(int i = 1;i <= 50;i++){
                    sum += i;
                }
                return sum;  //返回值
            }
        };
        //参数传入Callable对象callable
        FutureTask<Integer> task = new FutureTask<>(callable); //创建FutureTask对象来保存返回结果
        Thread t = new Thread(task); //创建线程,参数传入FutureTask对象task
        t.start();
        System.out.println(task.get()); //task.get()获取到结果,并打印输出
    }
}


相关文章
|
1月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
7天前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
10 1
|
11天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
18 2
|
17天前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
26 0
|
1月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
31 7
|
2月前
|
Java
在Java多线程领域,精通Lock接口是成为高手的关键。
在Java多线程领域,精通Lock接口是成为高手的关键。相较于传统的`synchronized`,Lock接口自Java 5.0起提供了更灵活的线程同步机制,包括可中断等待、超时等待及公平锁选择等高级功能。本文通过实战演练介绍Lock接口的核心实现——ReentrantLock,并演示如何使用Condition进行精确线程控制,帮助你掌握这一武林秘籍,成为Java多线程领域的盟主。示例代码展示了ReentrantLock的基本用法及Condition在生产者-消费者模式中的应用,助你提升程序效率和稳定性。
33 2
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
26 2
|
2月前
|
Java
多线程同步新姿势:Lock接口助你“一统江湖”!
多线程同步新姿势:Lock接口助你“一统江湖”!
43 2
|
13天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
32 1
C++ 多线程之初识多线程
|
13天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
36 6