软件编程
位置:首页>> 软件编程>> java编程>> 详解Java中Callable和Future的区别

详解Java中Callable和Future的区别

作者:iamswf  发布时间:2023-07-25 21:18:58 

标签:Java,Callable,Future,区别

Java中为什么需要Callable

在java中有两种创建线程的方法:

一种是继承Thread类,重写run方法:

public class TestMain {
   public static void main(String[] args) {
       MyThread t1 = new MyThread();
       t1.start();
   }
}
class MyThread extends Thread {
   public void run() {
       System.out.println("MyThread running...");
   }
}

第二种是使用Runnable创建一个线程:

public class TestMain {
   public static void main(String[] args) {
       Runnable r1 = new Runnable() {
           @Override
           public void run() {
               System.out.println("Thread created with runnable running...");
           }
       };
       Thread t1 = new Thread(r1);
       t1.start();
   }
}

其实这两种方式,底层都是执行Thread类的run方法:

详解Java中Callable和Future的区别

无论使用这里的哪种方式创建线程,都无法在线程结束时return一个返回值。但是在非常多的场景下,我们都需要在线程执行结束时,将执行的结果封装为一个返回值返回给主线程(或者调用者线程)。因此java在1.5版本时,在java.util.concurrent包引入了Callable接口,用于线程执行完时return一个返回值。

Callable和Runnable的区别

Runnable和Callable都是接口,分别定义如下:

package java.lang;

/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* @since   JDK1.0
*/
@FunctionalInterface
public interface Runnable {
   public abstract void run();
}
package java.util.concurrent;

/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread.  A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
   /**
    * @return computed result
    * @throws Exception if unable to compute a result
    */
   V call() throws Exception;
}

可以看出,CallableRunnable主要有两点区别:

  • 有返回值;

  • 可以抛出异常(这里抛出的异常,会在future.get()时可以通过ExectionException捕获);

因此可以看出,Callable更加实用。这里举个Callable使用的例子:

Callable callable = new Callable<Integer>() {
 @Override
 public Integer call() throws Exception {
   int i = new Random().nextInt(5);
   try {
     Thread.sleep(i * 1000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }
   return i;
 }
};

虽然Callable接口的call方法可以返回执行结果,但是有两个问题需要解决:

  • 线程的创建只能通过Runnable,通过Callable又如何创建线程?

  • 如何获取执行结果?

答案是FutureRunnableFuture

Future和RunnableFuture

Future是一个接口,看下定义:

package java.util.concurrent;

/**
* A {@code Future} represents the result of an asynchronous
* computation.  Methods are provided to check if the computation is
* complete, to wait for its completion, and to retrieve the result of
* the computation.  The result can only be retrieved using method
* {@code get} when the computation has completed, blocking if
* necessary until it is ready.  Cancellation is performed by the
* {@code cancel} method.  Additional methods are provided to
* determine if the task completed normally or was cancelled. Once a
* computation has completed, the computation cannot be cancelled.
* If you would like to use a {@code Future} for the sake
* of cancellability but not provide a usable result, you can
* declare types of the form {@code Future<?>} and
* return {@code null} as a result of the underlying task.
*
* @see FutureTask
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this Future's {@code get} method
*/
public interface Future<V> {

   /**
    * 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 {@code cancel} is called,
    * this task should never run.  If the task has already started,
    * then the {@code mayInterruptIfRunning} parameter determines
    * whether the thread executing this task should be interrupted in
    * an attempt to stop the task.
    *
    * <p>After this method returns, subsequent calls to {@link #isDone} will
    * always return {@code true}.  Subsequent calls to {@link #isCancelled}
    * will always return {@code true} if this method returned {@code true}.
    *
    * @param mayInterruptIfRunning {@code true} if the thread executing this
    * task should be interrupted; otherwise, in-progress tasks are allowed
    * to complete
    * @return {@code false} if the task could not be cancelled,
    * typically because it has already completed normally;
    * {@code true} otherwise
    */
   boolean cancel(boolean mayInterruptIfRunning);

   /**
    * Returns {@code true} if this task was cancelled before it completed
    * normally.
    *
    * @return {@code true} if this task was cancelled before it completed
    */
   boolean isCancelled();

   /**
    * Returns {@code true} if this task completed.
    *
    * Completion may be due to normal termination, an exception, or
    * cancellation -- in all of these cases, this method will return
    * {@code true}.
    *
    * @return {@code true} if this task completed
    */
   boolean isDone();

   /**
    * Waits if necessary for the computation to complete, and then
    * retrieves its result.
    *
    * @return the computed result
    * @throws CancellationException if the computation was cancelled
    * @throws ExecutionException if the computation threw an
    * exception
    * @throws InterruptedException if the current thread was interrupted
    * while waiting
    */
   V get() throws InterruptedException, ExecutionException;

   /**
    * Waits if necessary for at most the given time for the computation
    * to complete, and then retrieves its result, if available.
    *
    * @param timeout the maximum time to wait
    * @param unit the time unit of the timeout argument
    * @return the computed result
    * @throws CancellationException if the computation was cancelled
    * @throws ExecutionException if the computation threw an
    * exception
    * @throws InterruptedException if the current thread was interrupted
    * while waiting
    * @throws TimeoutException if the wait timed out
    */
   V get(long timeout, TimeUnit unit)
       throws InterruptedException, ExecutionException, TimeoutException;
}

可以看出,Future可以用来表示线程的未来执行结果:一个容器,这个容器内将来存放的是线程的执行结果,线程执行完之前该容器内没有值,但是线程一旦执行成功(Callablecall方法返回之后),就会将结果存入该容器。从Future的接口定义可看出,Future不仅支持阻塞获取执行结果,还支持取消任务的执行,判断任务是否执行完成等。因此通过Future,主线程(或者调用者线程)可以跟进子现场的执行情况。

Callable其实和Runnable很像,都会执行一个任务,只不过Callable可以返回执行的结果。一般将执行结果封装到Future,调用者线程即可以通过Future获取Callable的执行结果了。因此,一般Callable会和Future搭配使用。

但是问题来了:java创建线程,需要Runnable,获取执行结果又需要Future。因此RunnableFuture来了:

详解Java中Callable和Future的区别

可以看出,通过RunnableFuture,既可以创建线程,又可以获取线程的执行结果,当然RunnableFuture也是一个接口,我们一般情况下会使用它的具体实现类FutureTask

那可能又有人要问了,Callable又是如何建立联系的呢?看下FutureTask的使用方式就明白了:

public class TestMain {
   public static void main(String[] args) {
       Callable callable = new Callable<Integer>() {
           @Override
           public Integer call() throws Exception {
               int i = new Random().nextInt(5);
               try {
                   Thread.sleep(i * 1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               return i;
           }
       };

       /**
        * callable创建futureTask
        * FutureTask实现了RunnableFuture接口,因此即是Runnable又是Future
        * 作为Runnable可以传入Thread创建线程并执行
        * 作为Future,可以用来获取执行的结果。
        * 这里创建出来的futureTask对象有人称为"具柄"或者"存根",大家可以理解为用来获取线程执行结果的一个"引用"即可。
        */
       FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);

       // 作为Runnable使用
       Thread thread = new Thread(futureTask);
       thread.start();

       try {
           // 作为Future使用
           Integer integer = futureTask.get();
           System.out.println(integer);
       } catch (InterruptedException e) {
           e.printStackTrace();
       } catch (ExecutionException e) {
           e.printStackTrace();
       }
   }
}

因此FutureTaskCallableRunnable的桥梁。

不使用Callable和Future,仅使用Runnable实现相同功能

下面我们看下,如果不使用CallableFuture,仅使用Runnable如何实现返回值。

public class TestMain {
   public static void main(String[] args) {
       MyRunnable myRunnable = new MyRunnable();
       Thread t1 = new Thread(myRunnable);
       t1.start();
       Object o = myRunnable.get();
       System.out.println(o);
   }
}

class MyRunnable implements Runnable {
   // 存储执行结果
   private Object outCome = null;

   @Override
   public void run() {
       int i = new Random().nextInt(5);
       try {
           Thread.sleep(i * 1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       // 存储执行结果
       outCome = i;
       // 产出结果后唤醒等待的get方法
       synchronized (this) {
           notifyAll();
       }
   }

   public synchronized Object get() {
       while(outCome == null) {
           try {
               // 等待产出结果
               wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       return outCome;
   }
}

可以看出,通过Runnable实现更加麻烦,因此这也体现出了Callable+Future的优势。

来源:https://juejin.cn/post/7168111807515754527

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com