Java并发编程:解读Thread、Callable、Runnable、FutureTask的关系

之前学习并发编程的时候,对这几个接口和类就比较模糊。在单点学习完之后,还应该拿出来进行比较、找出关联关系,这样才能有更深的理解。✊加油!

Thread类和Runnable接口

创建线程的方式

多线程里有个经典的面试题:创建线程有哪几种方式?当时硬背下来的方式:

  1. 自定义类继承Thread类,并重写run方法,直接new该子类,并启动线程。
  2. 自定义类实现Runnable接口,并重写run方法。将该子类对象传给new Thread构造方法,并启动线程。
  3. 创建线程池,向线程池提交Runnable任务、Callable任务、FutureTask任务。

(但如果再深入问一下,肯定就说不上来了😂)

创建线程的第1、2两种方式,代码分别如下:

自定义类继承Thread,并重写run方法

java 复制代码
public class PrintStoryExtends extends Thread{
    String text;
    long interval;

    public PrintStoryExtends(String text, long interval){
        this.text = text;
        this.interval = interval;
    }

    public void run(){
        try{
            System.out.println("执行这段代码的线程名字是:" + Thread.currentThread().getName());
            printStory(text, interval);
            System.out.println(Thread.currentThread().getName() + "执行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    protected void printStory(String text, long interval) throws InterruptedException {
        for (char ch : text.toCharArray()){
            Thread.sleep(interval);
            System.out.print(ch);
        }
        System.out.println("----");
    }
}

//main函数所在类
public class PrintStoryAppMain {
    public static final String text = "今天又是阳光明媚的一天,某小胖7点钟起床洗漱完成,奔向地铁站去往浦东图书馆,在龙阳路换乘时,发现今天有好多人啊," +
            "还都是背着小书包的,某小胖心里想:从地铁站就开始卷了嘛,等下一下车要奔跑哇。";

    public static void main(String[] args) {
        System.out.println("程序执行开始,执行线程的名字叫做:" + Thread.currentThread().getName());
        for (int i = 1; i <= 2; i++){
            Thread thread = new PrintStoryExtends(text, 200 * i);
            thread.start();;
        }
        System.out.println("启动线程结束,名字叫做:" + Thread.currentThread().getName());
    }
}

在main方法中new PrintStoryExtends之后,调用start方法,会创建一个线程(一定要调用start方法,才会创建线程,如果直接调用run方法,只是用主线程去执行了普通的run方法),该线程执行run方法中的业务逻辑。

自定义类实现Runnable接口,并重写run方法。new Thread的时候,传入该自定义类

java 复制代码
public class PrintStoryImplements implements Runnable{
    private String text;
    private long interval;


    public PrintStoryImplements(String text, long interval){
        this.text = text;
        this.interval = interval;
    }
    @Override
    public void run() {
        try{
            System.out.println("执行这段代码的线程名字叫做" + Thread.currentThread().getName());
            printStory(text, interval);
            System.out.println(Thread.currentThread().getName() + "执行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void printStory(String text, long interval) throws InterruptedException{
        for (char ch : text.toCharArray()){
            Thread.sleep(interval);
            System.out.print(ch);
        }
        System.out.println();
    }
}

// main方法,创建并启动线程,执行
public class PrintStoryAppMain {
    public static final String text = "今天又是阳光明媚的一天,某小胖7点钟起床洗漱完成,奔向地铁站去往浦东图书馆,在龙阳路换乘时,发现今天有好多人啊," +
            "还都是背着小书包的,某小胖心里想:从地铁站就开始卷了嘛,等下一下车要奔跑哇。";

    public static void main(String[] args) {
        System.out.println("程序执行开始,执行线程的名字叫做:" + Thread.currentThread().getName());
        for (int i = 1; i <= 2; i++){
            Thread thread = new Thread(new PrintStoryImplements(text, 200 * i), "我的线程-" + i);
            thread.start();;
        }
        System.out.println("启动线程结束,名字叫做:" + Thread.currentThread().getName());
    }
}

为什么new Thread的时候可以传入实现了Runnable接口的对象?

Thread类和Runnable接口的关联

Runnable源码,非常简单

java 复制代码
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

Thread源码

java 复制代码
public class Thread implements Runnable {
 
    // 构造方法,传入Runnable对象和线程的名称,调用init方法进行线程的初始化
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
    // run方法,重写了Runnable接口的run方法
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

根据源码,可以很明显的知道:

  • Thread类实现了Runnable接口,并重写了run方法。
  • Thread类的构造函数支持传入Runnable类型的对象(即实现了Runnable接口的子类),并调用init方法初始化线程
  • 如果new Thread的时候传入了Runnable类型的对象(target),则执行Thread类的run方法的时候,会去执行target的run方法(也就是实现了Runnable接口的子类的run方法,这里面写了具体的业务逻辑。如上述例子中PrintStoryImplements类的run方法。)

Callable接口、Runnable接口

Callable接口源码

上述两种创建线程的方式,都无法获取到线程执行之后的返回结果(Runnable接口的run方法返回值是void),所以有了Callable接口。

Callable接口的源码

java 复制代码
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

这是一个泛型接口,call方法有返回值,如果不能输出返回值,也会抛出异常,可以清楚知道线程的执行情况。

Callable接口如何使用

创建线程的时候,Thread的构造方法可以传入Runnable对象,但是没有构造方法可以直接传入Callable对象。

Callable接口通常和FutureTask类结合使用。FutureTask类实现了RunnableFuture接口,该接口继承自Runnable和Future接口(详见后续小节)。

使用创建Callable类型的线程步骤:

  • 自定义类实现Callable接口
  • 将上述自定义类传入FutureTask的构造函数中,初始化一个FutureTask对象
  • 将上述futureTask对象传入new Thread方法创建线程(FutureTask间接实现了Runnable接口,所以可以传入Thread的构造函数)
  • 线程调用start方法进行启动
  • 可通过futureTask对象的get方法获取上述线程的执行结果

代码如下

java 复制代码
public class MyCallableTask implements Callable<String> {
    public String call() throws Exception {
        System.out.println("我是实现了Callable接口的线程");
        return "Callable线程结束";
    }
}


public class MyCallableTaskMain {
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<>(new MyCallableTask());
        Thread thread = new Thread(futureTask);
        thread.start();

        //获取线程执行结果
        String result = futureTask.get(2000, TimeUnit.MILLISECONDS);
        System.out.println(result);
    }
}

Future、RunnableFuture、FutureTask

由FutureTask向上追溯。

FutureTask类

FutureTask类源码

java 复制代码
public class FutureTask<V> implements RunnableFuture<V> {

    //构造方法,可传入Callable对象
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    // 可传入Runnable对象
    // Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

    //以下几个方法,都是Future接口中的方法
    public boolean isCancelled() {
        return state >= CANCELLED;
    }

    public boolean isDone() {
        return state != NEW;
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

    //重写的RunnableFuture接口的方法,即Runnable接口的方法
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

}

RunnableFuture接口源码

源码

csharp 复制代码
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

总结

  • FutureTask间接实现了Runnable接口(因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行)、Future接口。
  • FutureTask重写了Runnable接口的run方法,核心逻辑是:。。。
  • FutureTask重写了Future接口的几个对线程执行结果进行处理的方法。所以可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。
  • FutureTask既是Future、Runnable,又包装了Callable(如果是Runnable最终也会被转换为Callable ),它是这两者的合体。

Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消查询是否完成获取结果设置结果操作。

FutureTask相当于对Callable 进行了封装,管理Callable任务执行的情况,存储了 Callablecall 方法的任务执行结果。

Future模式

Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务,处理完成后,再通过Future获取计算结果。

这其实就是多线程中经典的 Future 模式,你可以将其看作是一种设计模式,核心思想是异步调用,主要用在多线程领域,并非 Java 语言独有。

Future接口

Future接口是一个泛型接口,位于 java.util.concurrent 包下,其中定义了 5 个方法,主要包括下面这 4 个功能:

  • 取消任务;
  • 判断任务是否被取消;
  • 判断任务是否已经执行完成;
  • 获取任务执行结果。
java 复制代码
// V是Future执行任务的任务返回值类型
public interface Future<V> {

    // 取消任务执行。成功返回true,否则返回false;
    boolean cancel(boolean mayInterruptIfRunning);

    // 判断任务是否被取消
    boolean isCancelled();

    // 判断任务是否已完成
    boolean isDone();

    // 获取任务执行结果
    V get() throws InterruptedException, ExecutionException;
    
    //获取任务执行结果。若指定时间内没有返回计算结果,则抛出抛出TimeoutException异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Callable和Future的关系

  • 两者是通过FutureTask关联的
  • FutureTask实现了Runnable接口和Future接口
  • FutureTask的构造函数可以传入Callable对象,相当于对Callable 进行了封装,管理Callable任务执行的情况,存储了 Callablecall 方法的任务执行结果。

参考文章

  1. Thread, Runable, Callable 还傻傻分不清?
  2. JavaGuide面试题
  3. 彻底搞懂Future、Callable、FutureTask、Runnable
  4. Java多线程第十三篇--盘一盘晕头转向的Runnable、Callable、Future、RunnableFuture、FutureTask
  5. Java中的Runnable、Callable、Future、FutureTask的区别与示例
相关推荐
coderWangbuer15 分钟前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
攸攸太上20 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
Kenny.志23 分钟前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba41 分钟前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
千里码aicood2 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455662 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
数字扫地僧3 小时前
HBase与Hive、Spark的集成应用案例
后端
架构师吕师傅3 小时前
性能优化实战(三):缓存为王-面向缓存的设计
后端·微服务·架构
bug菌3 小时前
Java GUI编程进阶:多线程与并发处理的实战指南
java·后端·java ee