📜  Java中的可调用和未来

📅  最后修改于: 2022-05-13 01:54:50.047000             🧑  作者: Mango

Java中的可调用和未来

先决条件:线程,多线程

对 Callable 的需求

有两种创建线程的方法——一种通过扩展 Thread 类,另一种通过使用 Runnable 创建线程。然而,Runnable 中缺少的一个特性是我们不能让线程在它终止时返回结果,即当 run() 完成时。为了支持此功能, Java中存在 Callable 接口。

可调用与可运行

  • 为了实现 Runnable,需要实现 run() 方法,它不返回任何内容,而对于 Callable,需要实现 call() 方法,它在完成时返回结果。请注意,不能使用 Callable 创建线程,只能使用 Runnable 创建线程。
  • 另一个区别是 call() 方法可以抛出异常,而 run() 不能。

必须重写以实现 Callable 的方法签名。

public Object call() throws Exception;

这是一个示例 Callable 的代码,它将在大约 0 到 4 秒的延迟后返回一个随机数。

// Java program to illustrate Callable
// to return a random number
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
  
class CallableExample implements Callable
{
  
    public Object call() throws Exception
    {
        // Create random number generator
        Random generator = new Random();
  
        Integer randomNumber = generator.nextInt(5);
  
        // To simulate a heavy computation,
        // we delay the thread for some random time
        Thread.sleep(randomNumber * 1000);
  
        return randomNumber;
    }
}

未来

call() 方法完成后,必须将 answer 存储在主线程已知的对象中,以便主线程可以知道线程返回的结果。以后程序如何存储和获取这个结果呢?为此,可以使用 Future 对象。将 Future 视为一个保存结果的对象——它现在可能不保存它,但它会在将来保存它(一旦 Callable 返回)。因此,Future 基本上是主线程可以跟踪其他线程的进度和结果的一种方式。要实现这个接口,必须重写 5 个方法,但由于下面的示例使用了库中的具体实现,因此这里只列出了重要的方法。

请注意,Callable 和 Future 做了两件不同的事情——Callable 与 Runnable 相似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从不同线程获得的结果。事实上,Future 也可以与 Runnable 一起工作,当 Executors 出现时,这一点就会变得清晰。

  • public boolean cancel(boolean mayInterrupt):用于停止任务。如果任务尚未开始,它将停止任务。如果它已启动,则仅当 mayInterrupt 为真时才中断任务。
  • public Object get() throws InterruptedException, ExecutionException:用于获取任务的结果。如果任务完成,则立即返回结果,否则等待任务完成,然后返回结果。
  • public boolean isDone():如果任务完成返回真,否则返回假

要创建线程,需要 Runnable。要获得结果,需要 Future。

Java库具有具体类型 FutureTask,它实现了 Runnable 和 Future,方便地结合了这两种功能。
可以通过为其构造函数提供 Callable 来创建 FutureTask。然后将 FutureTask 对象提供给 Thread 的构造函数来创建 Thread 对象。因此,间接地,线程是使用 Callable 创建的。为了进一步强调,请注意无法使用 Callable 直接创建线程。

这是使用 Callable 和 FutureTask 的完整示例的代码。

// Java program to illustrate Callable and FutureTask
// for random number generation
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
  
class CallableExample implements Callable
{
  
  public Object call() throws Exception
  {
    Random generator = new Random();
    Integer randomNumber = generator.nextInt(5);
  
    Thread.sleep(randomNumber * 1000);
  
    return randomNumber;
  }
  
}
  
public class CallableFutureTest
{
  public static void main(String[] args) throws Exception
  {
  
    // FutureTask is a concrete class that
    // implements both Runnable and Future
    FutureTask[] randomNumberTasks = new FutureTask[5];
  
    for (int i = 0; i < 5; i++)
    {
      Callable callable = new CallableExample();
  
      // Create the FutureTask with Callable
      randomNumberTasks[i] = new FutureTask(callable);
  
      // As it implements Runnable, create Thread
      // with FutureTask
      Thread t = new Thread(randomNumberTasks[i]);
      t.start();
    }
  
    for (int i = 0; i < 5; i++)
    {
      // As it implements Future, we can call get()
      System.out.println(randomNumberTasks[i].get());
  
      // This method blocks till the result is obtained
      // The get method can throw checked exceptions
      // like when it is interrupted. This is the reason
      // for adding the throws clause to main
    }
  }
}

输出:

4
2
3
3
0

线程启动后与线程的所有交互都使用 FutureTask 对象,因为它实现了 Future 接口。因此,无需存储 Thread 对象。使用 FutureTask 对象,可以取消任务、检查是否完成或尝试获取结果。

这是仅使用 Runnable 的代码。

// Java program to illustrate Runnable
// for random number generation
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
  
class RunnableExample implements Runnable
{
    // Shared object to store result
    private Object result = null;
  
    public void run()
    {
        Random generator = new Random();
        Integer randomNumber = generator.nextInt(5);
  
        // As run cannot throw any Exception
        try
        {
            Thread.sleep(randomNumber * 1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
  
        // Store the return value in result when done
        result = randomNumber;
  
        // Wake up threads blocked on the get() method
        synchronized(this)
        {
            notifyAll();
        }
    }
  
    public synchronized Object get()
          throws InterruptedException
    {
        while (result == null)
            wait();
  
        return result;
    }
}
  
// Code is almost same as the previous example with a
// few changes made to use Runnable instead of Callable
public class RunnableTest
{
    public static void main(String[] args) throws Exception
    {
        RunnableExample[] randomNumberTasks = new RunnableExample[5];
  
        for (int i = 0; i < 5; i++)
        {
            randomNumberTasks[i] = new RunnableExample();
            Thread t = new Thread(randomNumberTasks[i]);
            t.start();
        }
  
        for (int i = 0; i < 5; i++)
            System.out.println(randomNumberTasks[i].get());
    }
}

样本输出

0
4
3
1
4
2

参考

  • https://docs.oracle.com/javase/7/docs/api/java Java
  • https://docs.oracle.com/javase/7/docs/api/java Java
  • https://docs.oracle.com/javase/7/docs/api/java Java