Brief analysis of util.concurrent.future interface in Java

  • 2020-04-01 03:58:07
  • OfStack

In a single-threaded application, when you call a method it returns the result only after the computation has finished. Comes from Apache Commons IO):
 


public String downloadContents(URL url) throws IOException {
  try(InputStream input = url.openStream()) {
    return IOUtils.toString(input, StandardCharsets.UTF_8);
  }
}
 
//...
 
final Future<String> contents = downloadContents(new URL("http://www.example.com"));

DownloadContents () seems harmless, but it takes an arbitrarily long time to complete. At the same time, in order to reduce the delay, you may need to handle other work independently while waiting for the result. Previously you might start a new thread or wait for results (Shared memory, lock, bad wait()/notify() pairs).

Through Future< T> Pattern, it will become clear:
 


public static Future<String> startDownloading(URL url) {
  //...
}
 
final Future<String> contentsFuture = startDownloading(new URL("http://www.example.com"));
//other computation
final String contents = contentsFuture.get();

We'll achieve startDownloading (), startDownloading () will not be blocked, but wait outside the site response, do you understand this principle is very important. Conversely, if it returns quickly, return a lightweight Future< String> Object. This object is a promise so the Future string type is available, and we don't know when, but we'll keep the reference until it returns a result, so you can get it by future.get (). In other words, Future is a proxy or a wrapper around an object, not a real target object. Once the asynchronous computation is complete, you can extract it. So what interface does Future offer?

Future.get() is the most important method. It blocks and waits until the promised result is available, so if we really need the string, we call the get() method and wait. There is also an overloaded version that accepts a timeout parameter, so you don't have to wait forever if something goes wrong, and a TimeoutException is thrown after the set time.


In some cases, you may want to sneak around to see if Future is available. This can be done with isDone(). Imagine a situation where your user is waiting for some asynchronous computation, and you want him to know about it while he does some other computation:
 


final Future<String> contentsFuture = startDownloading(new URL("http://www.example.com"));
while (!contentsFuture.isDone()) {
  askUserToWait();
  doSomeComputationInTheMeantime();
}
contentsFuture.get();

Finally, the contents of the future.get () call are guaranteed to return immediately and not be blocked, because future.isdone () returns true. If you follow this pattern, you won't be busy alternate waiting and calling isDone() a million times per second.


Removing futrues is the last one we haven't covered yet. Imagine that you've started asynchronous work and you can only wait a few seconds, if after two seconds, we give up, either pass the error, or adopt a temporary solution to it. However, you are a good citizen, you should tell this future object: I do not need you, you forget it. Then you can save resources by stopping outdated tasks. The syntax is simple:
 


contentsFuture.cancel(true);  //meh...


We all like to hide Boolean type arguments, right? Cancellation can be done in two ways: by passing the false parameter before the task starts, provided that the result of the Future expression is evaluated before it starts. Once the callable.call () is halfway through, we want it to end, and if we pass true, future.call () becomes invasive and tries to interrupt the running work. Do you think it's good? Methods that throw InterruptedException, such as thread.sleep (), object.wait (), condition.await (), etc., even future.get (). If you are blocked in this method and someone decides to cancel your call, they will undoubtedly throw InterruptionException and issue a request for someone to interrupt the currently running task.


So we now know what Future is - a placeholder that allows you to get the target object in the Future. Like for a car, there are no keys. But how do you get an instance of Future in your application? The two most common resources are thread pools and asynchronous methods (thread pool support). Therefore, startDownloading () method can be rewritten as:
 


private final ExecutorService pool = Executors.newFixedThreadPool(10);
 
public Future<String> startDownloading(final URL url) throws IOException {
  return pool.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
      try (InputStream input = url.openStream()) {
        return IOUtils.toString(input, StandardCharsets.UTF_8);
      }
    }
  });
}


Although there are a number of tedious syntax problems, the basic idea is simple: wrap long-running calculations into callable. String> , and submit() to the thread pool, which contains 10 threads. Return to Future< String> Is implemented as if it were somehow linked to your task and thread pool. Obviously your task will not be executed immediately, instead it will be put in a queue and pulled out later by a thread, and now you need to figure out what the two special meanings of cancel() are -- you can cancel tasks that are in the queue, or you can cancel tasks that are already running, but this is a complicated thing.


You can also meet Future in Spring and EJB. For example, in the Spring framework you can add an @async annotation to a method:


@Async
public Future<String> startDownloading(final URL url) throws IOException {
  try (InputStream input = url.openStream()) {
    return new AsyncResult<>(
        IOUtils.toString(input, StandardCharsets.UTF_8)
    );
  }
}


Note that we simply implement Future by wrapping the result into AsyncResult, but this method itself does not interact with the thread pool or asynchronous processing. Later in the Spring will all the call agent to startDownloading () and execution in the thread pool. In ejbs, the same feature is done with @asynchronousannotation.


Related articles: