java.lang.Runnable是一个接口,只有一个run()方法
public interface Runnable { public abstract void run(); }
run()方法的返回值是void,故在执行完任务后无法返回任何结果
Callable是java.util.concurrent包下的,也是一个接口,也只有一个call()方法,类似于java.lang.Runnable的run()方法,实现Callable接口的类和实现Runnable接口的类都是可以被其它线程执行的任务
public interface Callable {
V call() throws Exception;
}
可以看到call()方法是有返回值的,可以将执行的结果返回
(1)Callable中定义的是call()方法,Runnable中定义的是run()方法
(2)Callable中的call()方法可以返回执行任务后的结果,Runnable中的run()方法无法获得返回值
(3)Callable中的call()方法定义了throws Exception抛出异常,抛出的异常可以在主线程
(4)Future.get()时被主线程捕获;Runnable中的run()方法没有定义抛出异常,运行任务时发生异常时也会上抛,因为即使不加默认也会上抛RuntimeException,但异常无法被主线程获取
(5)运行Callable任务可以拿到一个Future对象代表异步运算的结果
另外,Executors类中内部实现了RunableAdapter类,通过它可以将一个Runable和一个返回值result封装为一个Callable对象。
Future是java.util.concurrent包下的一个接口,代表着一个异步计算的结果,可以通过get()获取线程执行的返回值
Future提供了三种功能:
(1)获取任务执行的结果
(2)取消任务
(3)判断任务是否完成 或 是否取消
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
public class FutureTask<V> implements RunnableFuture<V>
FutureTask实现了RunnableFuture接口,那么RunnableFuture又是什么呢?
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation * unless it has
been cancelled.
*/
void run();
}
RunnableFuture接口继承了Runnable和Future,所以它既是一个可以让线程执行的Runnable任务,又是一个可以获取Callable返回值的Future
private volatile int state;
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;
/** The underlying callable; nulled out after running */
private Callable callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
(1)构造方法参数是Callable定义的任务,并将state置为NEW,只有当state为NEW时,callable才能被执行;
(2)构造方法参数为Runnable和带泛型的result对象,由于Runnable本身是没有返回值的,故线程的执行结果通过result返回;可以看到通过runnable和result封装了个Callable,实际上是new RunnableAdapter(task, result),这个Adapter适配器将Runnable和result转换成Callable,并返回result;
get()方法有两个实现,一个是一直等待获取结果,直到任务执行完;一个是等待指定时间,超时后任务还未完成会上抛TimeoutException
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
内部通过awaitDone()对调用线程进行阻塞,具体实现如下:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
//如果有超时,基于当前时间计算最终超时时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
//1.如果调用线程设置了中断标记,则从等待队列中移除节点并抛出异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 2. 获取当前状态,如果状态大于COMPLETING
// 说明任务已经结束(要么正常结束,要么异常结束,要么被取消)
// 则把thread显示置空,并返回结果
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 3. 如果状态处于中间状态COMPLETING
// 表示任务已经结束但是任务执行线程还没来得及给outcome赋值
// 这个时候让出执行权让其他线程优先执行,等待状态发生改变后返回
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
// 4. 如果等待节点为空,则构造一个等待节点
q = new WaitNode();
else if (!queued)
// 5.如果还没有入队列,则把当前节点加入waiters指向的队列的对头
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
// 如果需要等待特定时间,则先计算要等待的时间
// 如果已经超时,则删除对应节点并返回对应的状态
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 6.阻塞等待特定时间
LockSupport.parkNanos(this, nanos);
}
else
// 7.阻塞等待直到被其他线程唤醒
LockSupport.park(this);
}
}
上面这段代码有两个生僻函数的使用
(1) compareAndSwapObject
改方法解决的是并发中原子的更新引用对象
//如果offset指向的值等于expected,则原子的将Java变量更新为x
compareAndSwapObject(Object o, long offset, object expected, object x)
(2)LockSupport
LockSupport为Java并发的工具类,park表示表示阻塞当前线程,另外还有unpark表示唤醒阻塞的线程;
awaitDone方法执行的结果就是,在任务未执行结束时,所有希望获取结果的线程都将在waiters指向的队列中进行等待;添加的顺序为,后执行get的线程被添加在队首。在threadA和threadB都阻塞等待之后的waiters结果如图。
report方法根据awaitDown返回的状态进行判断,然后返回任务执行的结果或异常。
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);
}
public void run() {
//1
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//2
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
//3
setException(ex);
}
//4
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;
//5
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
(1) 标记1
状态如果不是NEW,说明任务或者已经执行过,或者已经被取消,直接返回;状态如果是NEW,则尝试把当前执行线程保存在runner字段中;
(2) 标记2
执行任务,如果任务执行过程中出现了异常则执行“标记3”,顺利的话就会执行“标记4”;
(3) 标记3
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
原子的改变状态为COMPLETING,然后将异常结果复制给outcome,再将状态设置为EXCEPTIONAL;这样就完成了前面说的状态变化过程:NEW->COMPLETING->EXCEPTIONAL,并且将最终结果保存在outcome中;最后调用finishCompletion方法,唤醒所有等待的线程并返回结果outcome。
(4) 标记4
set方法和setException方法调用基本相同,唯一不同的是状态变化过程:NEW->COMPLETING->NORMAl;
(5) 标记5
如果执行任务时,cancel被调用了,那么状态将被修改为INTERRUPTING或CANCELLED,handlePossibleCancellationInterrupt方法将被执行
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
该方法的理解需要结合cancel方法进行应用场景的举例:
当任务正在执行但并未结束,用户调用了cancel方法且参数为true,那么状态被设置INTERRUPTING
,此时任务执行完成开始调用handlePossibleCancellationInterrupt方法,因为cancel方法还未执行如下代码
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
所以会调用Thread.yield去放弃cpu的资源停止运行,直到上面的代码被执行状态变INTERRUPTED,最后任务运行结束。
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);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
其核心作用就是换新waiters队列中所有等待的线程,唤醒的方法就是使用LockSupport.unpark,在get方法中是使用的park方法让线程休眠的。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
(1) 修改状态为NEW-> INTERRUPTING-> INTERRUPTED或NEW-> CANCELLED;
(2) 如果是调用参数为true,则会设置认为执行线程的中断标记,这个中断标记如果是单纯的从FutrueTask来看是没有任何作用的,它的作用在于callable所执行的任务是否调用了阻塞方法并且捕获了InterruptedException异常进行处理,否则调用t.interrupt并无作用;
(3) 最后调用finishCompletion线程唤醒等待队列;
答案是“不一定”,根据JDK中的方法注释“Attempts to cancel execution of this task”,即尝试去取消执行的任务;
如果任务正在执行,且调用cancel()时参数mayInterruptIfRunning传的是true,那么会对执行线程调用interrupt()方法。
那么问题就变成了interrupt()方法能中断线程执行吗?
interrupt()方法不会中断正在运行的线程。这一方法实际上完成的是在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait()、Thread.join()、Thread.sleep()等阻塞,那么它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如果线程没有被阻塞,调用interrupt()将不起作用
那么即使线程正在阻塞状态,并抛出了InterruptedException,线程能否真的取消执行还要看代码中是否捕获了InterruptedException和有没有做相应的对中断标示的判断逻辑