本文将带您了解
1. 如何获取线程返回结果
2. 如何判定线程执行超时
3. 线程超时如何取消执行
4. 如何监视线程执行
①继承Thread类创建线程类
②通过Runable接口创建线程类
③通过Callable接口创建线程
下面通过例子看看这三种创建方式
1、继承Thread类(run 方法的返回类型是void)
public class MyThread extends Thread {
@Override
public void run() {
.....
}
}
2、实现runnable接口(run 方法的返回类型是void)
public class RunnableTest implements Runnable {
@Override
public void run() {
.....
}
}
3、实现Callable接口(call 方法的返回类型是 具体类型)
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
......
return 9527;
}
}
区别:
1、继承和实现接口创建,这个不是本文讨论重点,细节不谈
2、Callable 有返回线程执行结果(这个结果可以自定义类型和内容),重点,往下
/**
* 创建线程的方式:Callable
* @Author Tan
*/
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + " 执行。。。");
TimeUnit.SECONDS.sleep(3L);
return 9527;
}
public static void main(String[] args) {
/*
* @Date: 2020-06-09 17:11
* Step 1: 创建
*/
CallableTest runnableTest = new CallableTest() ;
FutureTask<Integer> futureTask = new FutureTask<>(runnableTest);
/*
* @Date: 2020-06-09 17:12
* Step 2: 执行
*/
new Thread(futureTask,"CallableTest").start();
/*
* @Date: 2020-06-09 17:12
* Step 3: 获取执行结果
*/
try {
Integer integer = futureTask.get();
System.out.println("执行结果:" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
执行日志
这里引进FutureTask 来获取执行结果,先用FutureTask将线程实例封装,然后用它创建线程并start,最后在 futureTask 中获取线程执行返回的结果
值得注意的是:通过get方法获取执行结果,该方法会阻塞直到任务返回结果,有点类似 Thread类的 join
上面获取线程结果的例子中, FutureTask是让我们取得线程执行结果的载体,看看这个类如何实现
>Runnable:接口,有run方法,实现者重写run即可。很多与线程相关的类都实现该接口重写run执行线程,如最熟悉的Thread
>Future:接口,提供对执行结果的处理方法
A Future represents the result of an asynchronous computation.
Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.
The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready.
Cancellation is performed by the cancel method. Additional methods are provided to determine if the task completed normally or was cancelled.
Once a computation has completed, the computation cannot be cancelled.
If you would like to use a Future for the sake of cancellability but not provide a usable result, you can
declare types of the form Future> and return null as a result of the underlying task.
– 来自源码 Future 类注释说明
Future 表示异步计算的结果。提供方法来检查计算是否完成,等待其完成,并获取计算结果。
当计算完成时只能用 get 方法获取计算结果,get方法必须阻塞等待计算完成。
取消可以通过执行cancel方法实现。提供了其他方法(isCancelled,isDone)来确定任务是正常完成还是被取消。
一旦计算完成,就不能取消计算。
如果为了可取消性而想使用Future,但不需要提供可用的执行结果,则可以使用声明形式Future>来获取任务返回null的结果。
>RunnableFuture:接口,继承上面的Future,和Runnable,意味着结合了这两个的方法
>FutureTask:实现类,实现了RunnableFuture。
A FutureTask can be used to wrap a Callable or Runnable object.
Because FutureTask implements Runnable, a FutureTask can be submitted
to an Executor for execution.
– 来自 源码 FutureTask类注释说明
FutureTask可用于包装Callable或Runnable对象。因为FutureTask实现了Runnable,所以可以将 FutureTask提交给Executor执行。
一句话说明用法,简洁直接,往回看之前的例子就好理解了。
在我们的例子中,也正是因为FutureTask实现了Runnable,所以才可以直接交给Thread去执行。下面从源码的角度看看我们怎么实现。
首先 CallableTest implements Callable 表明 CallableTest 是个 Callable
然后 FutureTask futureTask = new FutureTask<>(runnableTest); 就是 FutureTask包装Callable对象,这里包装了我们自定义的实例 CallableTest
这里看下FutureTask的构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
实际上就是传递callable,然后把状态变成 NEW(表示新建状态)
接着是 new Thread(futureTask,“CallableTest”).start();,前面说了,FutureTask 实现了 Runnable,所以可以用来创建Thread实例,看Thread的构造函数就知道了
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
到这里就是走线程执行的路线了,Thread.start之后,start0() 将线程的执行交给底层调度,得到调度权之后就执行Runnable.run,也就是我们定义的线程执行内容了
public synchronized void start() {
......
start0();
......
}
至此,整个封装,运行,结果获取的过程就完成了
等等,还有个重要的知识点!! get方法必须阻塞等待计算完成?
往回翻,发现在get方法里面有个语句 s = awaitDone(false, 0L);
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//如果没有达到返回条件时就一直循环
//这里就是通过循环完成的阻塞
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 状态变成完成之后就返回
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
无限循环阻塞,简直粗暴!!
FutureTask里面还提供了个方法用于检测线程执行时间
/**
* @throws CancellationException {@inheritDoc}
* timeout:多长时间获取结果(从线程执行开始算起)
* unit:设置时间单位,秒,分,小时等
*/
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)
// 超时会抛出 TimeoutException 异常,我们通过捕获这个异常就可以知道已经超时
throw new TimeoutException();
return report(s);
}
实现目标:传入时间参数,在指定时长内没有执行完就取消
用法很简单,就是在之前的基础上小小改动
/**
* 创建线程的方式:Callable
* @Author Tan
*/
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + " 执行。。。");
TimeUnit.SECONDS.sleep(3L);
return 9527;
}
public static void main(String[] args) {
/*
* @Date: 2020-06-09 17:11
* Step 1: 创建
*/
CallableTest runnableTest = new CallableTest() ;
FutureTask<Integer> futureTask = new FutureTask<>(runnableTest);
/*
* @Date: 2020-06-09 17:12
* Step 2: 执行
*/
new Thread(futureTask,"CallableTest").start();
/*
* @Date: 2020-06-09 17:12
* Step 3: 获取执行结果
*/
try {
// Integer integer = futureTask.get(); // 之前的
//(1)换成这个,表示3秒后获取线程执行结果(如果获取不到,会抛TimeoutException异常)
Integer integer = futureTask.get(3, TimeUnit.SECONDS);
System.out.println("执行结果:" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("执行超时"); //(2)捕获TimeoutException到异常,表明已超时
futureTask.cancel(true); //(3)取消线程执行
e.printStackTrace();
}
}
}
不过这样有个不足之处,没有办法获取超时线程的信息。如果处理超时交给另一个线程处理就更加难以获取,为此,我们重写futureTask。
public class MyFutureTask<V> extends FutureTask<V> {
private CallableTest runnableTest;
public MyFutureTask(Callable callable) {
super(callable);
runnableTest = (CallableTest) callable;
}
public CallableTest getRunner() {
return runnableTest;
}
}
/**
* 创建线程的方式:Callable
*
* @Author Tan
* @see FutureTask
*/
public class CallableTest implements Callable<Integer> {
// (1)定义一些需要的信息
private String runnerName;
@Override
public Integer call() throws Exception {
runnerName = Thread.currentThread().getName(); // (2)赋值
System.out.println(runnerName + " 执行。。。");
TimeUnit.SECONDS.sleep(10L);
return 9527;
}
public String getRunnerName() {
return runnerName;
}
public void setRunnerName(String runnerName) {
this.runnerName = runnerName;
}
public static void main(String[] args) {
/*
* @Date: 2020-06-09 17:11
* Step 1: 创建
*/
CallableTest runnableTest = new CallableTest();
MyFutureTask<Integer> futureTask = new MyFutureTask<>(runnableTest);
/*
* @Date: 2020-06-09 17:12
* Step 2: 执行
*/
new Thread(futureTask, "CallableTest").start();
/*
* @Date: 2020-06-09 17:12
* Step 3: 获取执行结果
*/
try {
Integer integer = futureTask.get(3, TimeUnit.SECONDS);
System.out.println("执行结果:" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("执行超时:" + futureTask.getRunner().getRunnerName());// (2)获取futureTask的自定义信息
futureTask.cancel(true);
e.printStackTrace();
}
}
}
因为FutureTask实现了Runnable,所以可以将 FutureTask提交给Executor执行。
官方在类Future注释上给出了使用例子
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target)throws InterruptedException {
Future<String> future = executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) {
cleanup();
return;
}
}
}}
// FutureTask 类是 Future 的一个实现,间接实现Runnable,因此可以由 Executor 执行。
// 上面的 submit 结构可以替换为:
FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
executor.execute(future);
上面提供两种方式:
submit之后返回 Future 对象
先定义FutureTask实例,再拿去 execute
其实也好理解,就是submit有返回值,所以直接获取,而executor没有,但是能先定义,再执行。很多人说execute不能处理异常,这是错的,上面第二种就给出了答案,两种方式都可以通过future获取执行异常.
实际生产过程中我们都是用线程池去做些并发的任务,我们都知道,一旦线程交给线程池去管理,很难从外部去把控每个线程的运行。
为此,我们需要站在线程的上帝角度去管控线程,不妨把负责这个工作的线程叫作监视线程,一般情况,监视线程放在一个线程池里面。
比如,线程池A是执行业务的线程,线程池B是监视线程池A的监视线程池
这样可以解决一些场景:
1、某线程执行结果不符合要求,需要再次计算
2、某线程执行时间过长,需要终止
下面就 超时取消这个场景 写个简单的例子
/**
* 检查超时线程
* 超时取消
* @Author Tan
*/
public class CheckThread extends Thread {
private Future future;
public CheckThread(Future future) {
this.future = future;
}
public void run() {
try {
// 没有超时就直接返回,否则抛 TimeoutException。注意,这里会根据设定时间阻塞
String result = (String) future.get(5, TimeUnit.SECONDS);
System.out.println("--执行结果:" + result);
} catch (InterruptedException e) {
System.out.print("InterruptedException");
} catch (ExecutionException e) {
System.out.print("ExecutionException");
} catch (TimeoutException e) {
System.out.println(" ---执行超时");
future.cancel(true);
}
}
}
public class TimeoutExecutor {
// 执行线程池A
private static ExecutorService executorServiceA = Executors.newFixedThreadPool(5);
// 监视线程池B
private static ExecutorService timeoutExecutorB = Executors.newFixedThreadPool(5);
private static Integer tim = 20;
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i < tim; i++) {
int finalI = i;
Future<String> result = executorServiceA.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
if (finalI == 5) {
Thread.sleep(8000);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getName() + "输出:" + finalI;
}
});
// 将 A 执行结果 交给B 检查(异步检查)
timeoutExecutorB.execute(new CheckThread(result));
}
}
}
看的出来,第五个线程因为超时被取消了(要求5秒处结果,该线程休眠8秒),取消之后就中断睡眠了
关于 获取线程执行结果。以及Future的剖析 到此结束。
.
.
.
——完毕