【并发编程】 --- Runnable、Callable、Future和FutureTask之间的关系

源码地址:https://github.com/nieandsun/concurrent-study.git


1 创建一个线程的方式到底有几种???

在java中其实创建一个线程的方式本质上只有两种:

  • (1)继承Thread类
  • (2)实现Runnable接口

这个观点可以通过Thread类中的注释进行证明:
在这里插入图片描述
但是同时相信很多人也都知道,使用下面两种组合,也可以创建一个线程:

  • (1)FutureTask + Callable
  • (2)线程池+Future+Callable

这时我想有些人可能会懵逼,难道JDK源码中的注释还有问题??? —> 其实并不是。


2 如何使用FutureTask 、Future、Callable、线程池实现线程


2.1 FutureTask + Callable实现多线程

  • code
package com.nrsc.ch2.future;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

@Slf4j
public class FutureTaskDemo1 {

    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int num = 0;
            log.info("开始进行计算。。。");
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                num++;
            }
            return num;
        }
    }


    public static void main(String[] args) throws Exception {

        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        
        new Thread(futureTask).start(); //-----//
       
        Thread.sleep(1000);
        log.info("do something in main");

        //在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完
        //然后就可以从future里获取到结果了
        Integer result = futureTask.get();
        log.info("result: {}", result);
    }
}
  • 运行结果

在这里插入图片描述


2.2 线程池+Future+Callable 实现多线程

  • code
package com.nrsc.ch2.future;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
public class FutureDemo1 {

    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int num = 0;
            log.info("开始进行计算。。。");
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                num++;
            }
            return num;
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //线程池通过submit开启线程
        Future<Integer> future = executorService.submit(new MyCallable());

        Thread.sleep(1000);

        log.info("do something in main");

        //在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完
        //然后就可以从future里获取到结果了
        Integer result = future.get();
        log.info("result: {}", result);
        executorService.shutdown();
    }
}
  • 运行结果,和2.1基本一致:

在这里插入图片描述


3 Runnable、Callable、Future和FutureTask之间的关系

3.1 整体关系介绍

看过2中的两个栗子,我想你肯定也已经知道揭开这几个对象之间关系的突破口就是FutureTask
为什么这么说呢? 首先我们应该知道Thread的构造函数就下面这么几个。并且在1中也已经说过,Java中开启线程的方式要么是继承Thread类,要么是使用继承了Runnable接口的类。但是在2.1中我却在创建Thread类时传入了一个FutureTask ----》 那这说明这个FutureTask 肯定就是一个Runnable(多态)。
在这里插入图片描述
接下来我们来看一下FutureTask类的继承关系图:
在这里插入图片描述
由此可知FutureTask确实是Runnable接口的子类,同时它还实现了Future接口。


3.2 简单看一下源码

  • Runnable接口源码:
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • RunnableFuture接口源码:
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
  • 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;
}
  • Callable的源码:
@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;
}

接着再来看一下FutureTask中的方法
在这里插入图片描述


3.3 四者关系小结

通过前面的内容可知:

  • (1)JDK允许的创建线程的方式只有两种,而这两种方式都无法获取线程中任务运行结果的返回值
  • (2)为了想办法获取到各个线程的执行结果,在没办法改变创建线程方式的前提下
    • (2.1)出现了FutureTask,一种特殊的Runnable
    • (2.2)我们的线程开启后其实还是会走没有返回值的run()方法 —》 其实就是实现Runnable接口开启线程的那种方式
    • (2.3)但是在没有返回值的run()方法内部,可以调用因实现了Callable接口而重写的有返回值的call()方法
    • (2.4)在call()方法调用完之后,就可以获取到该线程的执行结果了
  • (3)Future的作用之一就是拿到线程的执行结果。

画个图的话,可以这样表示:
在这里插入图片描述


介绍到这里之后,有兴趣的可以看一看为什么使用线程池,传入一个Callable,返回的是一个Future。。。相信你肯定能看出个123来☺☺☺


end

发布了225 篇原创文章 · 获赞 319 · 访问量 53万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/105156819