除了Thread和Runnable,你还知道第三种创建线程的方式Callable吗

简介: 相信大多数学过多线程的同学都知道创建线程常见的有三种方式,一种是继承Thread类,一种是实现Runnable接口,最后一种就是Callable,今天主要是对最后不常见的Callable方式进行介绍。

一、为什么要Callable接口


既然有了前面两种接口,为什么还需要第三种呢?这是因为前两种方式存在着一种缺陷,我们先来看看前面两种实现的方式,然后再来揭晓:


class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("继承Thread");
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("实现了Runnable");
    }
}

现在我们观察里面的run方法,返回的都是void,也就是说这两种方式都不能返回处理后的结构。但是Callable接口的出现可以有效地解决这一问题。答案很简单。现在我们来看看如何实现。


二、Callable接口的使用


1、创建线程


我们先来创建一个实现了Callable接口的线程。

class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int retValue = 10;
        return retValue;
    }
}

如何去使用呢?像前面两个一样吗?这里面会稍微有点麻烦的地方,我们建好了,接下来就是使用了,不过我们直接介绍使用的话,不那么容易理解,所以我们这次反着来。


2、线程使用


我们先来看Thread的构造方法:

public Thread() {}
public Thread(Runnable target) {}
Thread(Runnable target, AccessControlContext acc) {}
public Thread(ThreadGroup group, Runnable target) {}
public Thread(String name) {}
public Thread(ThreadGroup group, String name) {}
public Thread(Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name,
     long stackSize) {}

这个源码我摘自jdk1.8,一共列举了9个构造函数,但是仔细观察就能发现,没有一个构造方法可以传入Callable接口,这也就意味着不能根据之前那种简单的方式来创建线程。这时候怎么办呢?那就得换一种思考方式。


既然线程能有返回值,不知道是否可以联想到一个函数式接口Future,我们以此为基点进行查询:


//1、FutureTask实现了RunnableFuture
public class FutureTask<V> implements RunnableFuture<V>
//2、RunnableFuture又是继承了Runnable和Future
public interface RunnableFuture<V> extends Runnable, Future<V>
//3、Future接口的常用方法
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

从上面的代码我们可以看出FutureTask这个实现类,既有了Runnable线程的特性,也有了Future可返回函数的特性。因此我们就可以使用FutureTask这个类来实现Callable的使用。


class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"进入callable");
        int retValue = 10;
        return retValue;
    }
}
public class Test3 {
    public static void main(String[] args) throws Exception{
        FutureTask<Integer> task = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(task,"线程A");
        thread.start();
        //线程运行结束,说明Callable接口的方法已经完成,此时我们就可以获取返回值
        System.out.println("Callable返回的结果是:"+task.get());
    }
}

这就是一个最基本的使用方法。当然Future还提供了很多其他的方法:


(1)cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。


参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;


如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,

若mayInterruptIfRunning设置为false,则返回false;


如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。


(2)isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。


(3)isDone方法表示任务是否已经完成,若任务完成,则返回true;


(4)get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;


(5)get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。


基本上就是这样。其实经常会配合着ExecutorService来使用,现在我们举个例子来看一下:


public class Test3 {
    public static void main(String[] args) throws Exception{
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<Integer>
                                        (new MyCallable());
        executor.submit(futureTask);
        executor.shutdown();
        //线程运行结束,说明Callable接口的方法已经完成,此时我们就可以获取返回值
        System.out.println("Callable返回的结果是:"+futureTask.get());
    }
}

常见的用法就是这么两个。因为Callable是接口,所以分析源码并没有什么意义。OK,针对这个Callable接口的介绍,就先到这里。


如有问题还请批评指正。

相关文章
|
25天前
|
Java C# Python
线程等待(Thread Sleep)
线程等待是多线程编程中的一种同步机制,通过暂停当前线程的执行,让出CPU时间给其他线程。常用于需要程序暂停或等待其他线程完成操作的场景。不同语言中实现方式各异,如Java的`Thread.sleep(1000)`、C#的`Thread.Sleep(1000)`和Python的`time.sleep(1)`。使用时需注意避免死锁,并考虑其对程序响应性的影响。
|
1月前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
106 11
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
51 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
38 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
44 1
|
1月前
|
Java
为什么一般采用实现Runnable接口创建线程?
因为使用实现Runnable接口的同时我们也能够继承其他类,并且可以拥有多个实现类,那么我们在拥有了Runable方法的同时也可以使用父类的方法;而在Java中,一个类只能继承一个父类,那么在继承了Thread类后我们就不能再继承其他类了。
26 0
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
36 0
|
3月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。