学习JDK之“Future机制==>多线程”
什么是Future接口
Future是java.util.concurrent.Future,是Java提供的接口,可以用来做异步执行的状态获取,它避免了异步任务在调用者那里阻塞等待,而是让调用者可以迅速得到一个Future对象,
后续可以通过Future的方法来获取执行结果。一个实例代码如下:
1 public class Test { 2 public static void main(String[] args) throws ExecutionException, InterruptedException { 3 //创建线程池 4 ExecutorService executor = Executors.newCachedThreadPool(); 5 Future future = executor.submit(new Task()); 6 //这一步get会阻塞当前线程 7 System.out.println(future.get()); 8 9 executor.shutdown(); 10 } 11 12 private static class Task implements Callable<Integer> { 13 14 @Override 15 public Integer call() throws Exception { 16 System.out.println("子线程在进行计算"); 17 Thread.sleep(2000); 18 return 1; 19 } 20 21 } 22 23 }
代码很简单,就是将一个Runnable、Callable的实例放到一个线程池里,就会返回一个Future对象。后续通过future.get()取得执行结果,但事实上代码并没有达到异步回调的结果,而是get时阻塞了。
Future原理
因为阅读源码东西太对,这里只是总结关键点,说太多也记不住,先看ExecutorService的submit接口定义,代码如下:
1 * @param task the task to submit 2 * @param <T> the type of the task's result 3 * @return a Future representing pending completion of the task 4 * @throws RejectedExecutionException if the task cannot be 5 * scheduled for execution 6 * @throws NullPointerException if the task is null 7 */ 8 <T> Future<T> submit(Callable<T> task);
简单分析:
入参是callable的实例,这个没用疑问
返回参数是Future对象
看代码实现类AbstractExecutorService:
1 public Future<?> submit(Runnable task) { 2 if (task == null) throw new NullPointerException(); 3 RunnableFuture<Void> ftask = newTaskFor(task, null); 4 execute(ftask); 5 return ftask; 6 }
1 public FutureTask(Runnable runnable, V result) { 2 this.callable = Executors.callable(runnable, result); 3 this.state = NEW; // ensure visibility of callable 4 }
新建了一个FutureTask对象,状态state是NEW。可能的状态转换是:
Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
继续,之后执行的就是FutureTask的run方法,代码如下:
1 public void run() { 2 if (state != NEW || 3 !UNSAFE.compareAndSwapObject(this, runnerOffset, 4 null, Thread.currentThread())) 5 return; 6 try { 7 Callable<V> c = callable; 8 if (c != null && state == NEW) { 9 V result; 10 boolean ran; 11 try { 12 result = c.call(); 13 ran = true; 14 } catch (Throwable ex) { 15 result = null; 16 ran = false; 17 setException(ex); 18 } 19 if (ran) 20 set(result); 21 } 22 } finally { 23 // runner must be non-null until state is settled to 24 // prevent concurrent calls to run() 25 runner = null; 26 // state must be re-read after nulling runner to prevent 27 // leaked interrupts 28 int s = state; 29 if (s >= INTERRUPTING) 30 handlePossibleCancellationInterrupt(s); 31 } 32 }
我们看上面的代码,分析一下:
先判断state状态,如果不是NEW说明执行完毕,直接return掉。
后面使用CAS操作,判断这个任务是否已经执行,这里FutureTask有个全局的volatile runner字段,这里通过cas将当前线程指定给runner。
这里可以防止callable被执行多次。
继续往下跟,查看finishCompletion方法:
FutureTask中有一个WaiteNode单链表,当执行futureTask.get()方法时,多个线程会将等待的线程的next指向下一个想要get获取结果的线程。
finishCompletion主要就是使用Unsafe.unpark()进行唤醒操作,代码如下:
1 /** 2 * Removes and signals all waiting threads, invokes done(), and 3 * nulls out callable. 4 */ 5 private void finishCompletion() { 6 // assert state > COMPLETING; 7 for (WaitNode q; (q = waiters) != null;) { 8 if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { 9 for (;;) { 10 Thread t = q.thread; 11 if (t != null) { 12 q.thread = null; 13 LockSupport.unpark(t); 14 } 15 WaitNode next = q.next; 16 if (next == null) 17 break; 18 q.next = null; // unlink to help gc 19 q = next; 20 } 21 break; 22 } 23 } 24 25 done(); 26 27 callable = null; // to reduce footprint 28 }
总结一下:
并发原子操作仍旧是利用的CAS原子比较,主要是unsafe类
线程的阻塞、等待、唤醒仍旧是利用类似阻塞队列的链表,里面维护一个链表结构,看链表节点定义:
1 /** 2 * Simple linked list nodes to record waiting threads in a Treiber 3 * stack. See other classes such as Phaser and SynchronousQueue 4 * for more detailed explanation. 5 */ 6 static final class WaitNode { 7 volatile Thread thread; 8 volatile WaitNode next; 9 WaitNode() { thread = Thread.currentThread(); } 10 }
FutureTask的get方法是阻塞的,利用自旋实现,也是最常用的方式,代码如下:
记住一点:JDK底层很多实现都是基于下面几个技术:
JDK底层如何控制并发,保证原子性------------CAS操作
JDK并发如何阻塞、唤醒线程--------------------单向链表或者双向链表队列,队列节点waitnode就是线程的id、状态、next节点等
JDK如何实现自旋操作,比如FutureTask的get方法----------------没有那么神奇,就是for循环等待
JDK如何共享线程数据-----------voliate
JDK如何隔离线程数据-------------ThreadLocal
Future的不足
Future其实是一种模式,如下图:
future很明显,虽然是异步执行,但是无法准确知道异步任务说明时候执行完毕,如果调用get方法,在异步没有执行完成时,还是阻塞;如果频繁get检测,效率不高。
所以,我理解,使用future的get操作应该在最后一步,其他操作都已经完成了,一个可以参考的例子:
1 private int awaitDone(boolean timed, long nanos) 2 throws InterruptedException { 3 final long deadline = timed ? System.nanoTime() + nanos : 0L; 4 WaitNode q = null; 5 boolean queued = false; 6 for (;;) { 7 if (Thread.interrupted()) { 8 removeWaiter(q); 9 throw new InterruptedException(); 10 } 11 12 int s = state; 13 if (s > COMPLETING) { 14 if (q != null) 15 q.thread = null; 16 return s; 17 } 18 else if (s == COMPLETING) // cannot time out yet 19 Thread.yield(); 20 else if (q == null) 21 q = new WaitNode(); 22 else if (!queued) 23 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, 24 q.next = waiters, q); 25 else if (timed) { 26 nanos = deadline - System.nanoTime(); 27 if (nanos <= 0L) { 28 removeWaiter(q); 29 return state; 30 } 31 LockSupport.parkNanos(this, nanos); 32 } 33 else 34 LockSupport.park(this); 35 } 36 }