过了个春节,感觉还是家里面舒服啊~~
最开始接触Java多线程时候,主要就是Thread和Runnable,即继承Thread或者实现Runnable接口,再通过start方法即可开一个线程。但是run方法是没有返回值的,就类似于泼出去的水,你是无法知道其内部执行状态的。当然可以通过一些特定的volatile变量来标识。
Java5之后,出现了一个新的接口Callable,它的功能就是Runable的功能,但是它能够返回一个执行结果。
下面看一个具体例子:
利用Callable来实现多线程
public class FutureTest {
public static void main(String[] args) {
CallableThreadDemo ctd = new CallableThreadDemo(); //一个Callable的线程
// 以Callable方式,需要FutureTask实现类的支持,用于接收运算结果
FutureTask<Integer> result = new FutureTask<Integer>(ctd);
new Thread(result).start(); //放到Thread里面执行。
try {
Integer sum = result.get(); // 获取前面callable的执行结果。
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* @author anla7856
* 实现Callable接口,返回Integer,返回计算的1到50的和
*/
class CallableThreadDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 50; i++)
sum += i;
return sum;
}
}
如上代码,并没有用到Runnable来开启一个线程,而是通过Callable的方式。Callable和Future,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,但是Runnable不会返回结果(上文已提),并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
下面看看Callable接口:
Callable
Callable接口定义很简单,就一个方法call方法,它是具有返回值并且能够抛出异常的:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
由上述定义可以分析:
- V代表执行的结果的泛型
- 能够抛出异常
- FunctionalInterface注释,一个函数式方法
下面对比的看看Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
不同之处就不多叙述。
Future
Future是用来获取结果的,它代表一个异步计算的结果,可以理解为它是一个线程的管家,可以尝试取消线程,或者查看线程
执行的状态,以及结果。例如,当向线程池(下一篇文章讲述)提交一个Callable对象时,可以用一个Future管理这个Callable以及获取这个Callable对象的结果。
//此段代码摘自JDK1.8
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;
}
}
下面看看Future接口具体内容:
public interface Future<V> {
/**
* 试图取消执行此任务。
* 如果任务已完成,已被取消或因其他原因无法取消,此尝试将失败。
* 如果成功,并且此任务在cancel调用时尚未开始,则此任务不应运行。
* 如果任务已经开始,那么mayInterruptIfRunning参数确定执行这个任务的线程是否应该被中断以试图停止任务。
* 在此方法返回后,随后的调用isDone()将始终返回true。如果此方法返回,后续调用isCancelled() 将始终true返回true。
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
*true如果此任务在正常完成之前取消,则返回。
*/
boolean isCancelled();
/**
* 这几种情况都会返回true:normal termination, an exception, or cancellation
*/
boolean isDone();
//获取返回结果
V get() throws InterruptedException, ExecutionException;
//有超时的获取返回结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
如上,Future可以看作Callable提交执行的管家,它可以对Callable本身做一些操作。
下面看看FutureTask:
FutureTask
FutureTask是一个支持取消行为的异步任务执行器,如果在当前线程中需要执行比较耗时的操作,但又不想阻塞当前线程时,可以把这些作业交给FutureTask,另开一个线程在后台完成,当当前线程将来需要时,就可以通过FutureTask对象获得后台作业的计算结果或者执行状态。
JDK1.7及之前,FutureTask 通过使用内部类Sync继承AQS来实现,内部使用的AQS的共享锁。
JDK1.8没有使用AQS,而是自己实现了一个同步等待队列,在结果返回之前,所有的线程都被阻塞,存放到等待队列中。
FutureTask实现RunnableFuture接口,而RnnableFutrue则是同时继承了Runnable和Future接口(注意和多继承区分)。
咋一看会有点糊涂,因为Runnable接口是没有返回值的,那么它要Future接口干吗呢?
而在上文例子中,则是通过以下两行代码执行的:
FutureTask<Integer> result = new FutureTask<Integer>(ctd);
new Thread(result).start(); //放到Thread里面执行。
这样一看会不会有点感觉,最终还是会传递一个Callable对象给FutureTask的,接下来看看其实现。
先看里面字段:
/**
* 初始与new,
* 然后可能会进入completing,此时outcome就会被设置。
* 或者进入interrupting,此时如果调用cancel,就会成功,只有这个阶段调用cancel。
*/
private volatile int state; //volatile状态
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; //已中断
private Callable<V> callable; //当running后,就会变为null
private Object outcome; //执行结果,非volatile,被state读写保护,利用state加锁。
private volatile Thread runner; //运行线程。
private volatile WaitNode waiters; //waiters,等待者。当前线程的等待着。
在FutureTask中,对线程的控制有7种状态,可能的状态有以下几种状态:
- NEW -> COMPLETING -> NORMAL
- NEW -> COMPLETING -> EXCEPTIONAL
- NEW -> CANCELLED
- NEW -> INTERRUPTING -> INTERRUPTED
FutureTask可以既可以接受Runnable,也能接受Callable类型线程,有两种构造方法:
public FutureTask(Callable<V> callable) {
//接受传入的callable,让FutureTask来帮他执行。
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; //设置状态。
}
以及:
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result); //利用Executors包装一个callable
this.state = NEW; // ensure visibility of callable
}
接下来依次看它的方法:
run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))
//如果已经被运行了。并且强行获取runner失败。那么直接返回,因为有人已经执行或者已经执行完了。
return;
try {
Callable<V> c = callable; //获取callable。
if (c != null && state == NEW) { //新的,自己开始执行。
V result;
boolean ran;
try {
result = c.call(); //执行call,或者结果result
ran = true; //表示已经执行玩了,用于最后set执行结果。
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); //有错误,就set错误返回
}
if (ran)
set(result);
}
} finally {
runner = null; //runner置空防止calls再次运行,因为concurrent线程已经运行玩了
int s = state; //state一定要runner置空后重新读取。
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
看看所涉及的handlePossibleCancellationInterrupt
方法:
/**
* Ensures that any interrupt from a possible cancel(true) is only
* delivered to a task while in run or runAndReset.
* 判断该任务是否正在响应中断,如果中断没有完成,则等待中断操作完成
* 同时一定程度上,cancel的效果可以传递过来到run方法中。
*/
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
上述run方法只执行一次,并且会改变相应的标志为,
而runAndReset则既不设置结果也不设置state仅仅是运行一次:
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
//尝试获取线程。
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
runner = null;
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
如上述代码,runAndReset代码和run方法基本一致,但是它不改变state以及result的值。
cancel方法
public boolean cancel(boolean mayInterruptIfRunning) { //cancel方法。
//如果state为new,并且尝试设置state值,但是cas设置失败,那么直接返回false。
if (!(state == NEW && //cas去设置state。
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) //设置为interrupting或者cancelled。
return false;
//设置成功
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
//当处于running状态,还要强行让他失败,那么只能中断interrupt了。
try {
Thread t = runner;
if (t != null)
t.interrupt(); //中断runner线程,也就是当前线程。
} finally { // final state
//最后改变state值。
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
所以从上面代码看出,只有当state为NEW状态或者INTERRUPTING时候,调用cancel才有可能有效。
下面看看finishCompletion
方法:
/**
* 完成计算。
* 清除并unpack所有waiting threads,触发done。
*/
private void finishCompletion() {
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t); //释放t。
}
WaitNode next = q.next; //一直到结尾。
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
get方法
get有两个方法,一个是有超时时间的,其中都是调用awaitDone方法:
即获取异步线程的执行的结果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
看awaitDone方法:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L; //过期时间。
WaitNode q = null; //一个waitNode。
boolean queued = false;
for (;;) {
if (Thread.interrupted()) { //如果当前线程已经interrupted了,那么就中断。
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) { //如果s完成了,就把q.thread为null,并且返回。
if (q != null)
q.thread = null;
return s; //返回状态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);
}
}
由上述方法返回一个状态s,在有report判断返回,看report方法:
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL) //正常结束。
return (V)x;
if (s >= CANCELLED) //说明中断或者取消了。
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
最后再来捋一捋FutureTask的思路,通过传入的Callable或者Runnable参数thread,最终转化为Callable,执行call方法,得到返回值。同时一个FutureTask里面实现了一个单向队列,但是这个单项队列并不是用来存储执行任务,我们知道FutureTask的get方法是阻塞性的,如果还没有计算出结果,那么就可能不会返回值,而是要当计算完成时候才会返回。
所以,当多个线程调用都调用get时候,此时如果还没有返回,那么就会把这些线程都阻塞起来,当完成后,在一个个把waiter都唤醒。
同时,FutureTask能够判定任务运行的状态,例如在构造方法中,state被设置为NEW
this.state = NEW; // ensure visibility of callable
而在运行时,set方法里面,则又会设置state为COMPLETING,一旦完成,则会设置为NORMAL:
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
同时如果出错,则会设置为EXCEPTIONAL。
在cancel方法里面,一开始就会尝试更改state,如果更改成功,最终在finally块里面将state设置为INTERRUPTED:
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
参考资料:
1. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html
2. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html
3. https://www.jianshu.com/p/1dd951412ff5