学习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     }
posted @ 2021-03-09 17:43  骚哥  阅读(179)  评论(0编辑  收藏  举报