ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)

目录

前言:

一、ThreadPoolExecutor的中的submit和FutureTask 

二、通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)

Runnable任务类型和Callable任务类型

Runnable接口、Callable接口创建线程

Runable和Callable任务类型的区别:

线程池的一些补充

 三、总结

线程的创建

Runnable和Callable 


前言:

ThreadPoolExecutor 的 void execute(Runnable command) 方法,利用这个方法虽然可以提交任务,但是却没有办法获取任务的执行结果(execute() 方法没有返回值)

而很多场景下,我们又都是需要获取任务的执行结果的。那 ThreadPoolExecutor 是否提供了相关功能呢?必须的,这么重要的功能当然需要提供了——那就是sumbit

  • execute只能提交Runnable类型的任务,无返回值。submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
  • execute在执行任务时,如果遇到异常会直接抛出,而submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常。

一、ThreadPoolExecutor的中的submit和FutureTask 

 Executors 本质上是 ThreadPoolExecutor 类的封装.

Executors类和ThreadPoolExecutor都是util.concurrent并发包下面的类, Executos下面的newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor、newCachedThreadPool底线的实现都是用的ThreadPoolExecutor实现的,所有ThreadPoolExecutor更加灵活。

Java 通过 ThreadPoolExecutor 提供的 3 个 submit() 方法和 1 个 FutureTask 工具类来支持获得任务执行结果的需求。下面我们先来介绍这 3 个 submit() 方法,这 3 个方法的方法签名如下。

 // 提交 Runnable 任务
Future<?>
    submit(Runnable task);

// 提交 Callable 任务
<T> Future<T>
    submit(Callable<T> task);

// 提文 Runnable 任务及结果引用
<T> Future<T>
    submit(Runnable task T result);

你会发现它们的返回值都是 Future 接口

Future 接口有 5 个方法

取消任务的方法 cancel()、判断任务是否已取消的方法 isCancelled()、判断任务是否已结束的方法 isDone()以及2 个获得任务执行结果的 get() 和 get(timeout, unit)

其中最后一个 get(timeout, unit) 支持超时机制。通过 Future 接口的这 5 个方法你会发现,我们提交的任务不但能够获取任务执行结果,还可以取消任务。不过需要注意的是:这两个 get() 方法都是阻塞式的,如果被调用的时候,任务还没有执行完,那么调用 get() 方法的线程会阻塞,直到任务执行完才会被唤醒。 

这 3 个 submit() 方法之间的区别在于方法参数不同,下面我们简要介绍一下。

  1. 提交 Runnable 任务 submit(Runnable task) :这个方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。
  2. 提交 Callable 任务 submit(Callable<T> task):这个方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果。
  3. 提交 Runnable 任务及结果引用 submit(Runnable task, T result):这个方法很有意思,假设这个方法返回的 Future 对象是 f,f.get() 的返回值就是传给 submit() 方法的参数 result。这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要你注意的是 Runnable 接口的实现类 Task 声明了一个有参构造函数 Task(Result r) ,创建 Task 对象的时候传入了 result 对象,这样就能在类 Task 的 run() 方法中对 result 进行各种操作了。result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。

那么既然Executor是对ThreadPoolExecutor的封装,那么通过Executor创建的线程池自然也同样有上述3个submit方法和1个FutureTask工具类。

二、通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)

package Thread;

/**
 * 用Callable和FutureTask创建线程
 */

import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;

public class CallableFutureTask {
    public static void main(String[] args) {
        //第一种方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();

        //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
        /*Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        Thread thread = new Thread(futureTask);
        thread.start();*/

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("主线程在执行任务");

        try {
            System.out.println("task运行结果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("所有任务执行完毕");
    }
}
class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("子线程在进行计算");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++)
            sum += i;
        return sum;
    }
}

Runnable任务类型和Callable任务类型

Runnable接口、Callable接口创建线程

首先我们要知道可以通过以下两种创建线程

  • 实现Runable接口,重写run方法。
  • 使用Callable接口 和 Future接口创建线程。(线程处于并发状态, 默认异步执行)

实现Runable接口,重写run方法。

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 线程创建——》实现Runnable接口,重写run方法
 */
class MyRunable implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runable接口,重写run方法");
    }
}
public class thread3 {
// 使用了线程池
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        MyRunable myRunable = new MyRunable();
        es.submit(myRunable); // 将我们的Runnable任务提交到我们线程池中
        es.shutdown();
//        FutureTask:是对Runnable和Callable的进一步封装,
//        相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多
    }
// 未使用线程池(只是Thread)
    public static void main1(String[] args) {
        MyRunable myRunable = new MyRunable();
        Thread thread = new Thread(myRunable);
        // Thread thread1 = new Thread(new MyRunable());
        thread.start();
    }
}

使用Callable和Future创建线程 

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。 

public interface Callable<V> {
    V call() throws Exception;
}

Java5提供了Future接口来接收Callable接口中call()方法的返回值。


Callable接口是 Java5 新增的接口,不是Runnable接口的子接口,所以Callable对象不能直接作为Thread对象的target。

针对这个问题,引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口,可以作为Thread对象的target 。同时,Java5提供了一个RunnableFuture接口的实现类:FutureTask ,FutureTask可以作为 Thread对象的target

 一个案例

package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
 * 和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
 *  同时创建对象的时候,
 * 》call()方法可以有返回值
 *
 * 》call()方法可以声明抛出异常
 * Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,
 * 这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。
 */
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("这是用Callable创建线程的一个尝试!");
        return "xixi";
    }
}
public class thread1 {
// 只是用来Thread
    public static void main1(String[] args) throws ExecutionException, InterruptedException {
        // 1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
        MyCallable myThread = new MyCallable(); // myThread是一个Callable对象

        //2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
        FutureTask<String> futureTask = new FutureTask<String>(myThread); // 与Callable关联

        //3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)——实质上还是以Callable对象来创建并启动线程
        // FutureTask实现Future接口,说明可以从FutureTask中通过get取到任务的返回结果,也可以取消任务执行(通过interreput中断)
        Thread thread = new Thread(futureTask, "有返回值的线程");
         thread.start();
        // 4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
        System.out.println("子线程的返回值" + futureTask.get()); //get()方法会阻塞,直到子线程执行结束才返回
    }
    // 使用了线程池
    public static void main2(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        MyCallable myCallable = new MyCallable();
        es.submit(myCallable); // 你直接把Callable任务丢给线程池,获取不到call返回值
        es.shutdown();
        // FutureTask:是对Runnable和Callable的进一步封装,
        //相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多
    }
    // 使用了线程池
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        MyCallable myCallable = new MyCallable();
        //2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
        FutureTask<String> futureTask = new FutureTask<String>(myCallable); // 与Callable关联
        es.submit(futureTask); // 你直接把Callable任务丢给线程池,获取不到call返回值
        System.out.println(futureTask.get()); // 通过futureTask打印返回值
        es.shutdown();
    }


}

使用Callable和Future创建线程的 总结

使用Callable和Future创建线程的步骤如下:(未使用线程池)
(1)定义一个类实现Callable接口,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象
(3)使用FutureTask对象作为Thread对象的target创建并启动线程
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

使用Callable和Future创建线程的步骤如下:(使用线程池)

(1)定义一个类实现Callable接口,并重写call()方法,该call()方法将作为线程执行体,并且有返回值

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

(3)创建线程池

(4)通过sumbit()把封装了Callable对象的futureTask提交到线程池中

(5)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

当然如果你用了线程池,你也可以直接提交把你实例化的Callable对象和Runnable对象提交线程池中(不用FutureTask封装)

但是这里,你直接把Callable任务丢给线程池,你获取不到call方法返回值

// FutureTask:是对Runnable和Callable的进一步封装,
//相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多

Runable和Callable任务类型的区别:

两者都可以被ExecutorService执行

  • Callable的call()方法只能通过ExecutorService的 submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future.
  • Runnable的run方法,无返回值,无法抛出经过检查的异常。Callable的call方法,有返回值V,并且可能抛出异常。
  • 将Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,
  • 并且会返回执行结果Future对象。
  • 将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,
  • 并且会返回执行结果Future对象,但是在该Future对象上调用的方法返回的是null.
     

线程池的一些补充

Runnable:

可以直接用execute或sumbit提交到线程池

Callable:

功能相比Runnable来说少很多,不能用来创建线程(要和Future接口一起),也不能直接扔给线程池的execute方法。但是其中的call方法有返回值

FutureTask 实现了 Runnable 和 Future 接口,由于实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果

 三、总结

线程的创建

线程的创建有4种方法

1、通过继承Thread类,重写其中的run方法。

2、通过实现Runnable接口,重写其中的run方法。

我们的Runnable任务可以直接作为new Thread中的target。

3、通过实现Callable类,重写其中的call方法

但是此时你得到的MyCallable实例(callable任务)不能直接作为new Thread()中的target,放到括号中。你需要通过FutureTask包装一下你的MyCallable,得到futureTask因为FutureTask实现了Runnable接口,所以futureTask可以作为Thread类的Target——》new Thread(futureTask)

上面我们说的是没有用到线程池的情况下。

如果使用了线程池,线程池的sumbit可以提交Runnable任务和Callable任务。但是execute只能提交Runnable任务。

//        FutureTask:是对Runnable和Callable的进一步封装,
//        相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多

Runnable和Callable 

1.实现Runnable/Callable接口相比继承Thread类的优势

(1)适合多个线程进行资源共享
(2)可以避免java中单继承的限制
(3)增加程序的健壮性,代码和数据独立
(4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类

2.Callable和Runnable的区别

(1) Callable重写的是call()方法,Runnable重写的方法是run()方法
(2) call()方法执行后可以有返回值,run()方法没有返回值
(3) call()方法可以抛出异常,run()方法不可以
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果


 

猜你喜欢

转载自blog.csdn.net/weixin_61061381/article/details/129161977