当前位置: 移动技术网 > IT编程>开发语言>Java > 简谈java并发FutureTask的实现

简谈java并发FutureTask的实现

2019年07月19日  | 移动技术网IT编程  | 我要评论
概述 在使用java多线程解决问题的时候,为了提高效率,我们常常会异步处理一些计算任务并在最后异步的获取计算结果,这个过程的实现离不开future接口及其实现类fut

概述

在使用java多线程解决问题的时候,为了提高效率,我们常常会异步处理一些计算任务并在最后异步的获取计算结果,这个过程的实现离不开future接口及其实现类futuretask。futuretask类实现了runnable, future接口,接下来我会通过源码对该类的实现进行详解。

使用

我们先看下futuretask中的主要方法如下,可以看出futuretask实现了任务及异步结果的集合功能。看到这块的方法,大家肯定会有疑问,runnable任务的run方法返回空,futuretask如何依靠该方法获取线程异步执行结果,这个问题,我们在下面给大家介绍。

//以下五个方法实现接口future中方法
public boolean iscancelled(); 
public boolean isdone(); 
public boolean cancel();
public v get() throws interruptedexception, executionexception;
public v get(long timeout, timeunit unit);
//实现接口runnable中方法
public void run();

我们在使用中会构造一个futuretask对象,然后将futuretask扔到另一个线程中执行,而主线程继续执行其他业务逻辑,一段时间后主线程调用futuretask的get方法获取执行结果。下面我们看一个简单的例子:

/**
* created by yuanqiongqiong on 2019/4/9.
*/
public class futuretasktest {
private static executorservice executorservice = executors.newfixedthreadpool(1);
public static void main(string []args) {
callable callable = new acccallable(1, 2);
futuretask futuretask = new futuretask(callable);
executorservice.execute(futuretask);
system.out.println("go to do other things in main thread");
try {
thread.sleep(1000);
} catch (interruptedexception e) {
e.printstacktrace();
}
system.out.println("go back in main thread");
try {
int result = (int) futuretask.get();
system.out.println("result is " + result);
} catch (interruptedexception e) {
e.printstacktrace();
} catch (executionexception e) {
e.printstacktrace();
}
}
static class acccallable implements callable<integer> {
private int a;
private int b;
public acccallable(int a, int b) {
this.a = a;
this.b = b;
}
@override
public integer call() throws exception {
system.out.println("acc a and b in threadid = " + thread.currentthread().getname());
return a + b;
}
}
}

输出结果为:

go to do other things in main thread
acc a and b in threadid = pool-1-thread-1
go back in main thread
result is 3

实现分析

在分析实现前,我们先想下如果让我们实现一个类似futuretask的功能,我们会如何做?因为需要获取执行结果,需要一个object对象来存执行结果。任务执行时间不可控性,我们需要一个变量表示执行状态。其他线程会调用get方法获取结果,在没达到超时的时候需要将线程阻塞或挂起。

因此需要一个队列类似的结构存储等待该结果的线程信息,这样在任务执行线程完成后就可以唤醒这些阻塞或挂起的线程,得到结果。futuretask的实际实现也是类似的逻辑,具体如下。

首先看下futuretask的主要成员变量如下:

//futuretask执行状态
private volatile int state;
//具体的执行任务,会在run方法中抵用callable.call()
private callable<v> callable;
//执行结果
private object outcome; 
//获取结果的等待线程节点
private volatile waitnode waiters;

对于执行状态,在源码中已经有了非常清晰的解释,这里我只是贴出源码,不在进行说明,具体如下:

/**
* possible state transitions:
* new -> completing -> normal
* new -> completing -> exceptional
* new -> cancelled
* new -> interrupting -> interrupted
*/
private static final int new = 0;
private static final int completing = 1;
private static final int normal = 2;
private static final int exceptional = 3;
private static final int cancelled = 4;
private static final int interrupting = 5;
private static final int interrupted = 6;

然后我们看下futuretask的构造函数,如下:

public futuretask(callable<v> callable) {
if (callable == null)
throw new nullpointerexception();
this.callable = callable;
this.state = new; // ensure visibility of callable
}
public futuretask(runnable runnable, v result) {
//构造函数传入runnable对象时调用静态工具类executors的方法转换为一个callable对象
this.callable = executors.callable(runnable, result);
this.state = new; // ensure visibility of callable
}

如前所述,futuretask的执行线程中会调用其run()方法执行任务,我们看下这块逻辑:

public void run() {
//1.如果执行状态不是new或者有其他线程执行该任务,直接返回
if (state != new ||
!unsafe.compareandswapobject(this, runneroffset,
null, thread.currentthread()))
return;
try {
callable<v> c = callable;
//2.如果执行状态是new,即任务还没执行,直接调用callable.call()方法获取执行结果
if (c != null && state == new) {
v result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (throwable ex) {
result = null;
ran = false;
//3.发生异常,更新status为exceptional,唤醒挂起线程
setexception(ex);
}
//4.如果结果成功返回,调用set方法将设置outcome,更改status执行状态,唤醒挂起线程
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);
}
}

我们看下set函数的实现,具体看下4中的执行:

protected void set(v v) {
//将执行状态变更为completing
if (unsafe.compareandswapint(this, stateoffset, new, completing)) {
//设置执行结果
outcome = v;
//设置执行状态为normal
unsafe.putorderedint(this, stateoffset, normal); // final state
//执行完成后处理操作,具体就是遍历阻塞链表,删除链表节点,并唤醒每个节点关联的线程
finishcompletion();
}
}

以上就是任务执行线程做的逻辑,以上逻辑也回答了futuretask如何得到执行结果的疑问。下面我们看下用户调用get方法获取执行结果时的实现逻辑,这个时候futuretask可能处理各种状态,即可能没有执行,执行中,已完成,发生异常等,具体如下:

public v get(long timeout, timeunit unit)
throws interruptedexception, executionexception, timeoutexception {
if (unit == null)
throw new nullpointerexception();
int s = state;
//执行状态是new或者completing时执行awaitdone将线程加入等待队列中并挂起线程
if (s <= completing &&
(s = awaitdone(true, unit.tonanos(timeout))) <= completing)
throw new timeoutexception();
//根据执行状态status进行结果封装
return report(s);
}
//我理解这块是get的核心逻辑
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;
}
//如果执行状态是正在执行,说明线程已经被加入到等待队列中,放弃cpu进入下次循环(真正的自旋)
else if (s == completing) // cannot time out yet
thread.yield();
//第一次进入循环,创建节点
else if (q == null)
q = new waitnode();
//将节点加入到等待队列中,waiters相当于头阶段,不断将头结点更新为新节点
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中的阻塞队列新加入的节点都在头结点并且next指向之前的头结点,waitars指针总是指向新加入节点,通过waitars可以遍历整个等待队列,具体截图如下。此外等待队列节点结构很简单成员变量只有线程引用和next指针,这里再列出器接口。

futuretask等待队列

读到这里,相信大家已经对futuretask的实现细节有了一定的认识。此外,futuretask没有使用锁而是使用unsafe的是cas的原子操作来解决竞争问题,减少了锁带来的上下文切换的开销,提高了效率。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网