前言
实现Runnable接口的线程类与一个缺陷,就是在任务执行完之后无法取得任务的返回值。 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦 。所以,从JDK 1.5开始,java提供了Callable接口,该接口和Runnable接口相类似,提供了一个call()方法可以作为线程的执行体,但是call()方法要比run()方法更为强大:# call()方法可以有返回值;# call()方法可以声明抛出异常。那么使用callable接口是如何获取返回值的呢?
一、Callable与Runnable
既然Callable接口可以看做是Runnable的“增强版”,那我们先看看Runnable接口的实现,追根溯源也是搬砖的普遍素质嘛~
public interface Runnable {
public abstract void run();
}
Runnable接口中只包含一个抽象方法run()返回值为void, 所以在执行完任务之后无法返回任何结果。接下来我们可以看看Callable接口
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()函数返回的类型就是传递进来的V类型。 那么该如何使用Callable接口呢?Callable接口不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target去运行;而且call方法还有返回值–call()方法并不是直接调用。
JDK提供了Future接口来代表call()方法里的返回值,并且为Future接口提供了一个实现类FutureTask,该类实现了Future接口,并实现了Runnable接口,其实该类也是Future接口的唯一实现类,所以FutureTask对象可以作为Thread的target。Callable一般配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:
/**
* Submits a value-returning task for execution and returns a
* Future representing the pending results of the task. The
* Future's <tt>get</tt> method will return the task's result upon
* successful completion.
*/
<T> Future<T> submit(Callable<T> task);
/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future's <tt>get</tt> method will
* return the given result upon successful completion.
*/
<T> Future<T> submit(Runnable task, T result);
/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future's <tt>get</tt> method will
* return <tt>null</tt> upon <em>successful</em> completion.
*/
Future<?> submit(Runnable task);
一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用 。
这里提一下ExecutorService的submit与execute方法的区别:
ExecutorService的submit与execute方法都能执行任务,但在使用过程,发现其对待run方法抛出的异常处理方式不一样。两者执行任务最后都会通过Executor的execute方法来执行,但对于submit,会将runnable物件包装成FutureTask,其run方法会捕捉被包装的Runnable Object的run方法抛出的Throwable异常,待submit方法所返回的的Future Object调用get方法时,将执行任务时捕获的Throwable Object包装成java.util.concurrent.ExecutionException来抛出。
而对于execute方法,则会直接抛出异常,该异常不能被捕获,想要在出现异常时做些处理,可以实现Thread.UncaughtExceptionHandler接口。
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。
二、Future接口
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下 :
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;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
-
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数 mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返 回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若 mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论 mayInterruptIfRunning为true还是false,肯定返回true。
-
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
-
isDone方法表示任务是否已经完成,若任务完成,则返回true;
-
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
-
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了FutureTask。
三、FutureTask实现类
我们先来看一下FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,我们再来看一下RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
FutureTask提供了2个构造器:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
如何有童鞋对FutureTask具体内容感兴趣,可以看一下这篇文章:FutureTask深入解析
四、使用示例
package com.blog.www.thread;
import com.blog.www.util.ListUtils;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.List;
import java.util.concurrent.*;
/**
* 第一个线程
* <br/>
*
* @author :leigq
* @date :2019/8/13 16:12
*/
public class FirstThread implements Callable<String> {
private List<String> list;
public FirstThread(List<String> list) {
this.list = list;
}
@Override
public String call() {
System.out.println(list);
return String.format("My name is %s", Thread.currentThread().getName());
}
}
class Test{
public static void main(String[] args) {
/*
* 多线程使用思路:
* 1、查询数据,每次查询 dataCount 条
* 2、开 threadCount 个线程处理数据,每个线程处理 dataCount / threadCount 条
* */
int dataCount = 100, threadCount = 20, maxThreadCount = 30;
// 使用 ThreadPoolExecutor 创建线程池,阿里 java 开发手册推荐,详见:https://blog.csdn.net/fly910905/article/details/81584675
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("test-pool-%d").build();
ExecutorService pool = new ThreadPoolExecutor(threadCount, maxThreadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
// 模拟 dataCount 条数据
List<String> strings = Lists.newArrayList();
for (int i = 0; i < dataCount; i++) {
strings.add(String.format("data %d", i));
}
// 平分数据
List<List<String>> lists = ListUtils.avgAssign(strings, threadCount);
// 保存执行结果
List<FutureTask<String>> futureTasks = Lists.newArrayList();
lists.forEach(list -> {
FirstThread firstThread = new FirstThread(list);
FutureTask<String> futureTask = new FutureTask<>(firstThread);
pool.submit(futureTask);
futureTasks.add(futureTask);
});
futureTasks.forEach(futureTask -> {
try {
System.out.println(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
pool.shutdown();
}
}
执行结果:
[data 0, data 1, data 2, data 3, data 4]
[data 10, data 11, data 12, data 13, data 14]
[data 5, data 6, data 7, data 8, data 9]
[data 15, data 16, data 17, data 18, data 19]
[data 20, data 21, data 22, data 23, data 24]
[data 25, data 26, data 27, data 28, data 29]
[data 30, data 31, data 32, data 33, data 34]
[data 35, data 36, data 37, data 38, data 39]
[data 40, data 41, data 42, data 43, data 44]
[data 45, data 46, data 47, data 48, data 49]
[data 50, data 51, data 52, data 53, data 54]
[data 55, data 56, data 57, data 58, data 59]
[data 60, data 61, data 62, data 63, data 64]
[data 65, data 66, data 67, data 68, data 69]
[data 70, data 71, data 72, data 73, data 74]
[data 75, data 76, data 77, data 78, data 79]
[data 80, data 81, data 82, data 83, data 84]
[data 85, data 86, data 87, data 88, data 89]
[data 90, data 91, data 92, data 93, data 94]
[data 95, data 96, data 97, data 98, data 99]
My name is test-pool-0
My name is test-pool-1
My name is test-pool-2
My name is test-pool-3
My name is test-pool-4
My name is test-pool-5
My name is test-pool-6
My name is test-pool-7
My name is test-pool-8
My name is test-pool-9
My name is test-pool-10
My name is test-pool-11
My name is test-pool-12
My name is test-pool-13
My name is test-pool-14
My name is test-pool-15
My name is test-pool-16
My name is test-pool-17
My name is test-pool-18
My name is test-pool-19
Process finished with exit code 0
上面用到了一个ListUtils.avgAssign(List<T> list, Integer n)
方法,如下:
package com.blog.www.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 集合工具类
* <br/>
*
* @author :leigq
* @date :2019/8/13 14:16
*/
public class ListUtils {
/**
* 按指定大小,分隔集合,将集合按规定个数分为 n 个部分
* <br/>
* create by: leigq
* <br/>
* create time: 2019/8/13 14:16
*
* @param list : 待拆分的集合
* @param elementCount : 元素个数
* @return 拆分后的集合
*/
public static <T> List<List<T>> split(List<T> list, Integer elementCount) {
if (list == null || list.isEmpty() || elementCount < 1) {
return Collections.emptyList();
}
List<List<T>> result = new ArrayList<>();
int size = list.size();
int count = (size + elementCount - 1) / elementCount;
for (int i = 0; i < count; i++) {
List<T> subList = list.subList(i * elementCount, (Math.min((i + 1) * elementCount, size)));
result.add(subList);
}
return result;
}
/**
* 将一个list均分成 n 个list
* <br/>
* create by: leigq
* <br/>
* create time: 2019/8/13 14:11
*
* @param list : 待拆分的集合
* @param n : 拆分数量
* @return 拆分后的集合, if list.size > n, 平分或者前几个集合有多个元素; if list.size < n, 后几集合为空
*/
public static <T> List<List<T>> avgAssign(List<T> list, Integer n) {
List<List<T>> result = new ArrayList<>();
// 先计算出余数
int remainder = list.size() % n;
// 然后是商
int number = list.size() / n;
//偏移量
int offset = 0;
for (int i = 0; i < n; i++) {
List<T> value;
if (remainder > 0) {
value = list.subList(i * number + offset, (i + 1) * number + offset + 1);
remainder--;
offset++;
} else {
value = list.subList(i * number + offset, (i + 1) * number + offset);
}
result.add(value);
}
return result;
}
}
还有一点需要注意,就是在线程类中注入Spring
的bean
:详见:https://www.cnblogs.com/xuyuanjia/p/6097875.html