Callable
- Callable是一个接口,约定了线程要做的事情,和Runnable一样,区别在于有返回值
- 代码
public interface Callable<V> { V call() throws Exception; }
Runnable接口
- 该接口指定了线程执行的任务,实现了此接口的类,即可作为线程执行的任务,进行执行
- 代码
public interface Runnable { public abstract void run(); }
Future接口
- 提供了对任务进行处理的方法,获取结果,取消任务
- 特点
- 定义了异步计算的接口,提供了计算是否完成的检查,等待完成和取回等方法
- 提供了阻塞获取结构的get方法
- 取消可以使用cancel方法,但一旦计算完成,就无法取消
- 源码
public interface Future<V> { //取消方法 //针对线程执行的状态,进行分析 //1. 如果任务已经完成,会直接返回取消成功 //2. 如果任务已经取消,会直接返回取消成功 //3. 如果任务还没有开始,发起取消是,可以取消成功的 //4. 如果取消时,任务已经在运行了,mayInterruptIfRunning为true的话,就可以打断运行中的线程,取消任务 //5. 如果取消时,任务已经在运行了,mayInterruptIfRunning为false的话,表示不能打断直接返回false boolean cancel(boolean mayInterruptIfRunning); //判断是否取消 boolean isCancelled(); //线程是否已经结束 boolean isDone(); //阻塞获取返回结果 //如果任务被取消了,返回CancellationException异常 //如果等待过程中被打断了,抛InterruptedException异常 V get() throws InterruptedException, ExecutionException; //等待,有超时时间的等待,如果超时之后仍然没有响应,抛TimeoutException异常 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
RunnableFuture接口
- 该接口继承了Future、Runnable接口,意味着实现该接口的类,可以作为线程任务执行,同时还具备了管理线程的能力
- 通过jdk学习,可以看到将每个功能都合理的细分接口,实现接口就意味着实现了接口所代表的功能
- 代码
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
FutureTask
- FutureTask实现了Runnable接口,具备了管理任务的能力,同时内部组合了Callable实例,可以获取任务的返回值,那内部是如何处理Runnable和Callable
1 基本属性
- 类定义
// 任务状态 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;//任务被打断成功 // 组合了 Callable private Callable<V> callable; // 异步线程返回的结果 private Object outcome; // 当前任务所运行的线程 private volatile Thread runner; // 记录调用 get 方法时被等待的线程 private volatile WaitNode waiters;
- 构造器
// 使用 Callable 进行初始化 public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; // 任务状态初始化 this.state = NEW; // ensure visibility of callable } // 使用 Runnable 初始化,并传入 result 作为返回结果。 // Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了 public FutureTask(Runnable runnable, V result) { // Executors.callable 方法把 runnable 适配成 RunnableAdapter, // RunnableAdapter 实现了 callable,所以也就是把 runnable 直接适配成了 callable。 this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
- 适配器模式
- FutureTask在构造方法时,使用了两种,一种是runnable,一种是callable,具体内部是使用的callable,而对于runnable而言,使用适配器模式,将runnable转换成一种callable类,进行统一使用。这样对外使用的时候,就不用区别使用runnable的方法还是callable的方法,统一归为callable的方法,一不小心学到了!!!
- 代码
public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); //runnable适配器,转换成callable的子类 return new RunnableAdapter<T>(task, result); }
- 适配器类,实现了Callable接口,内部封装了Runnable对象
static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
2 重要方法
2.1 get获取返回值方法
- 源码
- 超时获取结果方法get
//通过抛出的异常,可以看出,可以响应中断,可以响应超时 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) throw new TimeoutException(); //返回执行结果 return report(s); }
- awaitDone:等待超时时间获取结果
- 一个新线程执行流程
1. 第一轮for循环,执行的逻辑是q == null,所以这时候会新建一个节点q。第一轮循环结束。
2. 第二轮for循环,执行的逻辑是!queue,这个时候会把第一轮循环中生成的节点的next指针指向waiters,然后CAS的把节点q替换waiters。也就是把新生成的节点添加到waiters链表的首节点。如果替换成功,queued=true。第二轮循环结束。
3. 第三轮for循环,进行阻塞等待。要么阻塞特定时间,要么一直阻塞知道被其他线程唤醒。
- 一个新线程执行流程
private int awaitDone(boolean timed, long nanos) throws InterruptedException { //1. 计算出终止时间 final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; //不排队 boolean queued = false; //2. 自旋加入队列 for (;;) { //2.1 如果线程已经中断,之前抛出中断异常 if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; //2.2 如果线程已经结束,直接返回状态 if (s > COMPLETING) { if (q != null) q.thread = null; return s; } //2.3 如果正在执行,当前线程让出 cpu,重新竞争,防止 cpu 飙高 else if (s == COMPLETING) // cannot time out yet Thread.yield(); //2.4 默认第一次都会执行这里,执行成功之后,queued 就为 true,就不会再执行了,把当前 waitNode 当做 waiters 链表的第一个 else if (q == null) q = new WaitNode(); else if (!queued) //如果不在列表中,就把,q加入到链表的头部,让waiterOffset指向它 //最新执行的线程,会加到头结点的地方 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); //2.5 如果设置了超时时间,并过了超时时间的话,从 waiters 链表中删除当前 wait else if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } // 没有过超时时间,线程进入 TIMED_WAITING 状态 LockSupport.parkNanos(this, nanos); } //2.6 没有设置超时时间,进入 WAITING 状态 else LockSupport.park(this); } }
- 看来report是通过获取状态,然后获取结果,又是一种解耦的操作,无敌的源码,看一波report获取结果的方法,直接通过结果,然后判断状态返回,感觉就是为了校验状态,然后返回结果,结果的获取,然而并不是真正获取结果的方法,难受
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); }
- get方法虽然是为了获取结果,但是做了很多wait的动作
- 如果发现任务正在进行,没有完成,就阻塞当前线程
- 任务执行完成后返回结果。阻塞底层使用的是LockSupport.park方法,使当前线程进入WAITING或TIMED_WAITING状态。
2.2 run:任务执行的方法
- 类实现了Callable接口,也实现了Runnable接口,可想,该类肯定是作为任务执行,正如名称所示,所以任务最重的方法,是run方法,额,执行状态与任务解耦,与结果解耦
- 代码
public void run() { // 状态不是任务创建,或者当前任务已经有线程在执行了,直接返回 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { //调用执行 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } //将结果赋值给outcome值 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); } }
2.3 cancel:取消任务
- 代码
// 取消任务,如果正在运行,尝试去打断 public boolean cancel(boolean mayInterruptIfRunning) { if (!(state == NEW &&//任务状态不是创建 并且不能把 new 状态置为取消,直接返回 false UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; // 进行取消操作,打断可能会抛出异常,选择 try finally 的结构 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; }
- 清理线程的方法:finishCompletion,唤醒所有等待结果的线程,告知他们已经有结果了,可以返回了
private void finishCompletion() { // assert state > COMPLETING; 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 }
2.4 waiters
- waiters用来记录需要获取此任务执行结果的线程,类似于栈结构,通过next形成链表的结构,因为一个任务可能多个线程都在执行,最新执行的线程会加入到头部
- 节点的初始化,将当前的线程作为节点的线程属性,创建
static final class WaitNode { volatile Thread thread; volatile WaitNode next; WaitNode() { thread = Thread.currentThread(); } }
优秀文章:https://blog.csdn.net/Memery_last/article/details/79532630,包含图片