java异步调用future、callable以及futuretask分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Andyzhu_2005/article/details/82845138

Runnable与Callable

java中用于新建线程的有两大接口:Runnable和Callable。其中,Runnable用的比较多,多用于不需要子线程返回结果的处理。

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

通过接口说明可以发现,Runnable接口有个run方法,返回是void。我们在run方法中实现子线程的逻辑。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

而观察Callable接口,可以发现其方法call可以返回V类型的返回值。那么如何获取实现Callable接口的子线程的返回值呢。答案是:通过线程池的submit方法和实现了callable接口的对象,可以返回一个future接口的对象,通过此对象可以异步获取callable方法的运行结果。(一般callable与线程池一起使用)
future.get() 阻塞式的返回结果,如果任务未执行完毕,就阻塞等待结果产生。
future.get(10000, TimeUnit.MILLISECONDS); 在时间范围内得到结果,没有的话就报错
boolean cancel(boolean mayInterruptIfRunning);:取消子线程
boolean isCancelled();:判断子线程是否已取消
boolean isDone();:判断子线程是否已完成

public class FutureTest {

	/**
	 * @param args
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//建立线程池
		ExecutorService executorService=Executors.newCachedThreadPool();
		
		//建立callable的任务
		Callable<String> callable = new Callable<String>() {

			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				Thread.sleep(10000);
				return "茶叶准备好啦";
			}
		};
		
		//将callable任务放入线程池中		
		Future<String> submit = executorService.submit(callable);
		
		//可以先做自己的事
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("把开水准备好");
		
		//通过submit查询结果
		System.out.println("茶叶是否准备好?"+submit.isDone());
		
		String string = submit.get();
		
		System.out.println(string+"开水"+",可以泡茶啦");
		
		//关闭线程池
		executorService.shutdown();
	}
}

上面的 例子,通过executorService.submit()提交一个callable的异步任务(即假如让外卖送茶叶来(需要花10s)),在提交任务后,主线程可以干自己的事,即花了2s来烧开水。水烧开后,通过future接口的对象来查询任务是否已经完成(即茶叶是否送到),或者阻塞式的等待结果。最后关闭线程池。
一般来说,Runnable接口可以通过new Thread().start来实现,而Callable是通过线程池来提交。那么有没有方法可以让Callable接口通过new Thread().start来实现呢。答案就是下面的FutureTask来实现。

Futuretask

来看看javadoc的说明:

A cancellable asynchronous computation. This class provides a base implementation of Future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; the get methods will block if the computation has not yet completed. Once the computation has completed, the computation cannot be restarted or cancelled (unless the computation is invoked using runAndReset). 

A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution. 

In addition to serving as a standalone class, this class provides protected functionality that may be useful when creating customized task classes.

Type Parameters:
<V> The result type returned by this FutureTask's get methods
Since:
1.5
Author:
Doug Lea

翻译一下就是:可以取消的异步(线程)计算。是接口future的具体实现,提供了启动、取消、查询子线程是否完成以及异步(事后)查询子线程的结果等方法。其中get方法将会阻塞直至等待子线程的方法完成。当子线程方法完成后,子线程就不能再次启动或者取消。

public class FutureTask<V> implements RunnableFuture<V> {}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通过上面的代码可以看到,FutureTask实现了RunnableFuture接口,而RunnableFuture又继承了Runnable和Future接口。因此FutureTask即可以作为Runnable的实现类,又可以获取实现了Callable的接口的方法的返回结果。
再看看FutureTask的构造方法:


    public FutureTask(Callable<V> 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
    }

FutureTask有两个构造方法:一个是传入实现了Callable的实现类,通过FutureTask可以异步获得这个实现类的计算结果。另一个是传入实现了Runnable接口的实现类和基于泛型的返回参数。当Runnable的实现类的方法执行完毕后,就会返回这个泛型的参数。
其中 this.callable = Executors.callable(runnable, result);是将runnable的实现类转为基于callable接口的实现类。通过查看其实现可以证实:


   public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);   //这里的RunnableAdapter就是个适配器
    }


    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;
        }
        //重点在这里, 其实call的实现就是run方法执行完后返回result。
        public T call() {
            task.run();
            return result;
        }
    }

看到这里,就可以明白上一章节说到callable的实现类也可以通过new Thread().start()来实现。因为FutureTask也是实现了Runnable接口的,因此它可以通过new Thread().start()来启动,同时它的构造方法可以传入callable的实现类,因此callable的实现类也可以通过new Thread().start()来实现。

public class FutureTaskTest2 {

	/**
	 * @param args
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//实现callable接口的futuretask
		
		FutureTask<String> task=new FutureTask<>(new Callable<String>() {

			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				Thread.sleep(5000);
				return "test";
			}
		});
		
		Thread thread=new Thread(task);
		thread.start();
		System.out.println("main thread start");
		
		Thread.sleep(2000);
		
		System.out.println("task 是否完成"+task.isDone());
		
		System.out.println("等待task 完成"+task.get());
		
		
		
	  
	}
	
	
	

}

不过,一般子线程的调用,都建议用线程池来实现。下面的例子是ExecutorService+FutureTask的异步获取子线程结果的例子。


public class FutureTaskTest {

	/**
	 * @param args
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 * 例子里FutureTask的构造方法传入的是runnable,其实传入callable的也是类似的
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//建立线程池
		ExecutorService executorService=Executors.newCachedThreadPool();
		
		//建立runnable的任务
		Runnable runnable = new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("茶叶准备好啦");
			}
		};
		
		//将runnable任务放入线程池中
		String value="执行成功";
		FutureTask<String> task=new FutureTask<String>(runnable,value );
		
		executorService.execute(task);
		
		
		
		System.out.println("runnable运行状态"+task.isDone());
		
		String object = (String) task.get();
		
		
		
		System.out.println("runnable运行状态"+object);
	
		
	
		
		//关闭线程池
		executorService.shutdown();
	}
	
}

FutureTask的原理实现

FutureTask是future的具体实现,重点关注其几个方法。在分析方法之前,先看看其源码里的一个变量

/**
The run state of this task, initially NEW. The run state transitions to a terminal state only in methods set, setException, 
and cancel. During completion, state may take on transient values of COMPLETING (while outcome is being set) or 
INTERRUPTING (only while interrupting the runner to satisfy a cancel(true)). Transitions from these intermediate to final 
states use cheaper ordered/lazy writes because values are unique and cannot be further modified.
 Possible state transitions: 
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     * */

private volatile int state;  //其有7种状态。

    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;

state用于表示Futuretask里的子线程运行状态。
NEW:表示是个新的任务或者还没被执行完的任务。这是初始状态。
COMPLETING:任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态。
NORMAL:任务已经执行完成并且任务执行结果已经保存到outcome字段,状态会从COMPLETING转换到NORMAL。这是一个最终态。
EXCEPTIONAL:任务执行发生异常并且异常原因已经保存到outcome字段中后,状态会从COMPLETING转换到EXCEPTIONAL。这是一个最终态。
CANCELLED:任务还没开始执行或者已经开始执行但是还没有执行完成的时候,用户调用了cancel(false)方法取消任务且不中断任务执行线程,这个时候状态会从NEW转化为CANCELLED状态。这是一个最终态。
INTERRUPTING: 任务还没开始执行或者已经执行但是还没有执行完成的时候,用户调用了cancel(true)方法取消任务并且要中断任务执行线程但是还没有中断任务执行线程之前,状态会从NEW转化为INTERRUPTING。这是一个中间状态。
INTERRUPTED:调用interrupt()中断任务执行线程之后状态会从INTERRUPTING转换到INTERRUPTED。这是一个最终态。
有一点需要注意的是,所有值大于COMPLETING的状态都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消)。(转载自http://beautyboss.farbox.com/post/study/shen-ru-xue-xi-futuretask)

首先来看看run方法,这个方法是在FutureTask任务提交给线程池进行submit之后运行,是异步线程执行的方法内容。

    public void run() {
     // 1. 状态如果不是NEW,说明任务或者已经执行过,或者已经被取消,直接返回
    // 2. 状态如果是NEW,则尝试把当前执行线程保存在runner字段中
    // 如果赋值失败则直接返回
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;  //得到callable,callable是通过构造方法传递进来的
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();    //执行call方法的内容
                    ran = true;        
                } catch (Throwable ex) {
                    result = null;    //如果发生异常,ran状态就设为false  ,比如此线程执行一半,被外部的submit.cancel(true)
                    						//则此线程就被中断,发生异常
                    ran = false;
                    setException(ex);  //设置异常结果
                }
                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);
        }
    }


    /**
     * Sets the result of this future to the given value unless
     * this future has already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon successful completion of the computation.
     *
     * @param v the value
     */
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

    /**
     * Causes this future to report an {@link ExecutionException}
     * with the given throwable as its cause, unless this future has
     * already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon failure of the computation.
     *
     * @param t the cause of failure
     */
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

再看get方法

  public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)        //如果state<COMPLETING,说明异步计算还没结束
            s = awaitDone(false, 0L);   //等待异步计算执行完毕
        return report(s);
    }

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&     //如果还未执行完毕,而且等待的时间超出了timeout,就报错
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);  

//report根据state状态返回结果还是异常
   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);
    }
    }

重点看看awaitDone方法。

    //等待异步计算结束或者因为time超出而停止
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        //WaitNode是个有next指针的链表
        WaitNode q = null;    
        boolean queued = false;
        for (;;) {        // cas里经常用到的for死循环
            if (Thread.interrupted()) {     //这里的Thread是指get方法所属的thread,而不是task里callable所指的thread
            //如果调用get方法所属的thread被中断了,则将q移除出等待结果的链表
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {   //如果state状态>COMPLETING,说明异步计算有了结果。不用继续等待了。
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();   //同理,这里的Thread也是指外面调用get方法所属的thread。这里是让出cpu,允许futuretask
               								 //里的线程继续完成最后的赋值。
            else if (q == null)        //如果q==null,就建立一个waitNode。waitNode里的thread就是上述的Thread.yield 的thread
                q = new WaitNode();
            else if (!queued)    //如果q没有入队列,则将q放入waiters的首部,并且next指向waiters。即每个最新的q都是放在
            						//链表的首位
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();  //再次计算时间
                if (nanos <= 0L) {   //如果小于0,说明超时了,直接返回
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);   //否则将本线程(还是get方法所属的那个线程挂起规定的时间)
            }
            else
                LockSupport.park(this);   //如果没有时间限制,就挂起get方法所属的那个线程
        }
    }

上面就是get方法阻塞等待异步计算的结果的核心。从上述代码看到, LockSupport.park(this)操作将get所属的线程挂起。那么在哪儿给恢复呢。上上面代码的run()方法中的①的 set(result)方法。

  protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {  //先将state从new--》COMPLETING
            outcome = v;   //赋值
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state  ,
            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);   //在这里唤醒get所属的线程     ②
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

总结:
FutureTask用于异步获取子线程计算的结果。如果task的计算任务没完成,那么任何外部线程通过task.get()方法都会阻塞,这些阻塞线程会形成一个waiter链表。知道task的run方法执行完毕,才会依次恢复这些阻塞线程,从而获取到结果。

cancal(boolean mayInterruptIfRunning)方法

javadoc的解释:
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

After this method returns, subsequent calls to isDone will always return true. Subsequent calls to isCancelled will always return true if this method returned true.
翻译一下就是:这个方法用于尝试去取消task任务。但是如果task任务已经完成或者已经取消情况下会失败,而且有可能因为其他原因而不能取消。如果这个方法执行,而且task任务还未开始执行,那么task任务会取消。如果task任务已经执行,那么会根据mayInterruptIfRunning这个参数决定task是否继续。即,如果是false,那么task任务会继续执行,同时外部调用task的cancel所在的线程调用get方法获取结果会报异常。如果是true,那么task任务会取消,同时外部调用task的cancel所在的线程调用get方法获取结果会报异常。
看其源码

    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&     //如果state状态不是new,说明任务马上结束,不能取消
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {    //否则,如果是true
                try { 
                    Thread t = runner;    //这里的runner是指callable所指的线程,即task任务的线程
                    if (t != null)
                        t.interrupt();    //取消线程
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

例子1: task任务已经开始,然后调用cancel(true),看结果

public class FutureTaskCancelTest1 {

	/**
	 * @param args
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//建立线程池
		ExecutorService executorService=Executors.newCachedThreadPool();
		
		//建立runnable的任务
		Callable<String> callable=new Callable<String>() {

			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				System.out.println("task任务开始执行");
				Thread.sleep(5000);
				System.out.println("task任务结束执行");
				return "计算完成";
			}
		};
		
		FutureTask<String> task=new FutureTask<String>(callable);
		
		executorService.execute(task);
		 Thread.sleep(3000);
	    System.out.println("cancel方法返回值是否成功:"+task.cancel(true));
			
	   
		
		
		System.out.println("cancel运行状态"+task.isDone());
		
		
		String object = (String) task.get();
		
			
		System.out.println("获取task任务的计算结果"+object);
		
		//关闭线程池
		executorService.shutdown();
	}
		

}

结果

task任务开始执行
Exception in thread "main" cancel方法返回值是否成功:true
cancel运行状态true
java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.andy.jcu.future.FutureTaskCancelTest1.main(FutureTaskCancelTest1.java:65)

task任务开始后过了3s调用cancel(true)方法,最后一句 System.out.println(“task任务结束执行”);没有执行,说明task任务确实被取消了,同时task.get()报异常。

例子2: task任务已经开始,然后调用cancel(false),看结果

public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//建立线程池
		ExecutorService executorService=Executors.newCachedThreadPool();
		
		//建立runnable的任务
		Callable<String> callable=new Callable<String>() {

			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				System.out.println("task任务开始执行");
				Thread.sleep(5000);
				System.out.println("task任务结束执行");
				return "计算完成";
			}
		};
		
		FutureTask<String> task=new FutureTask<String>(callable);
		
		executorService.execute(task);
		 Thread.sleep(3000);
	    System.out.println("cancel方法返回值是否成功:"+task.cancel(false));
			
	   
		
		
		System.out.println("cancel运行状态"+task.isDone());
		
		
		String object = (String) task.get();
		
			
		System.out.println("获取task任务的计算结果"+object);
		
		//关闭线程池
		executorService.shutdown();
	}
		

结果:

task任务开始执行
cancel方法返回值是否成功:true
cancel运行状态true
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.andy.jcu.future.FutureTaskCancelTest1.main(FutureTaskCancelTest1.java:65)
task任务结束执行

如果任务已经执行,那么调用cancel(false),其task任务会继续执行完成。

例子2: task还没有开始,然后调用cancel(false),看结果

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// TODO Auto-generated method stub
		
		//建立线程池
		ExecutorService executorService=Executors.newCachedThreadPool();
		
		//建立runnable的任务
		Callable<String> callable=new Callable<String>() {

			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				System.out.println("task任务开始执行");
				Thread.sleep(5000);
				System.out.println("task任务结束执行");
				return "计算完成";
			}
		};
		
		FutureTask<String> task=new FutureTask<String>(callable);
		System.out.println("cancel方法返回值是否成功:"+task.cancel(false));
		 
		executorService.execute(task);
	
	    
			
	   
		
		
		System.out.println("task是否运行完成:"+task.isDone());
		
//		
		String object = (String) task.get();
		
			
		System.out.println("获取task任务的计算结果"+object);
		
		//关闭线程池
		executorService.shutdown();
	}
		

}

结果:

cancel方法返回值是否成功:true
task是否运行完成:true
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.andy.jcu.future.FutureTaskCancelTest1.main(FutureTaskCancelTest1.java:66)

在task任务还没开始前,调用cancel,会将任务取消。

那什么时候cancel返回false呢?
只有任务结束后,再取调用cancel,会返回false。

猜你喜欢

转载自blog.csdn.net/Andyzhu_2005/article/details/82845138