Thinking logic of computer programs (79), thinking 79
In the previous section, we mentioned that in asynchronous task programs, a common scenario is that the main thread submits multiple asynchronous tasks and then processes the results if a task is completed, in this scenario, Java provides a convenient method for sending and distributing packets. The CompletionService is an interface and its implementation class is ExecutorCompletionService, we will discuss them in this section.
Basic usage
Interface and Class Definition
Like ExecutorService introduced in section 77, CompletionService can also submit asynchronous tasks. The difference is that it can obtain results in the order of task completion, which is defined:
public interface CompletionService<V> { Future<V> submit(Callable<V> task); Future<V> submit(Runnable task, V result); Future<V> take() throws InterruptedException; Future<V> poll(); Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;}
The submit method is the same as ExecutorService. When the take and poll methods are used, they are used to obtain the result of the next task. take () will block the wait, and poll () will return immediately, if no completed task exists, null is returned. The poll method with the time parameter will wait for a limited time.
The main implementation class of CompletionService is ExecutorCompletionService, which relies on an Executor to complete the actual task submission, and is mainly responsible for queuing and processing of results. Its construction methods include:
public ExecutorCompletionService(Executor executor)public ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)
At least one Executor parameter is required. A BlockingQueue parameter can be provided to serve as the queue for task completion. If it is not provided, ExecutorCompletionService will create an external BlockingQueue.
Basic example
In the example of invokeAll in section 77, we demonstrated the concurrent download and Analysis of the URL title. In this example, the result is processed only after all tasks are completed. Here, let's modify it and output the result as soon as the task is completed. The Code is as follows:
public class CompletionServiceDemo { static class UrlTitleParser implements Callable<String> { private String url; public UrlTitleParser(String url) { this.url = url; } @Override public String call() throws Exception { Document doc = Jsoup.connect(url).get(); Elements elements = doc.select("head title"); if (elements.size() > 0) { return url + ": " + elements.get(0).text(); } return null; } } public static void parse(List<String> urls) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); try { CompletionService<String> completionService = new ExecutorCompletionService<>( executor); for (String url : urls) { completionService.submit(new UrlTitleParser(url)); } for (int i = 0; i < urls.size(); i++) { Future<String> result = completionService.take(); try { System.out.println(result.get()); } catch (ExecutionException e) { e.printStackTrace(); } } } finally { executor.shutdown(); } } public static void main(String[] args) throws InterruptedException { List<String> urls = Arrays.asList(new String[] { "http://www.cnblogs.com/swiftma/p/5396551.html", "http://www.cnblogs.com/swiftma/p/5399315.html", "http://www.cnblogs.com/swiftma/p/5405417.html", "http://www.cnblogs.com/swiftma/p/5409424.html" }); parse(urls); }}
In the parse method, an ExecutorService is created first, and then the CompletionService is created. It is convenient to submit tasks through the latter and process the results one by one in the order of completion?
Basic Principles
How does ExecutorCompletionService enable ordered processing of results? In fact, it is also very simple. As mentioned above, it has an additional queue. After each task is completed, the Future will represent the result.
Then the question is, after the task is completed, how do I know how to join the team? Let's take a look.
In section 77, we have introduced FutureTask. After the task is completed, the finishCompletion method is called, whether it is normal, abnormal, or canceled. This method calls a done method, the code for this method is:
protected void done() { }
Its implementation is empty, but it is a protected method, and the subclass can override this method.
In ExecutorCompletionService, the submitted Task Type is not a general FutureTask, but a subclass QueueingFuture, as shown below:
public Future<V> submit(Callable<V> task) { if (task == null) throw new NullPointerException(); RunnableFuture<V> f = newTaskFor(task); executor.execute(new QueueingFuture(f)); return f;}
This subclass overrides the done method and adds the result to the Completion queue when the task is completed. The code is:
private class QueueingFuture extends FutureTask<Void> { QueueingFuture(RunnableFuture<V> task) { super(task, null); this.task = task; } protected void done() { completionQueue.add(task); } private final Future<V> task;}
The take/poll method of ExecutorCompletionService is to get the result from this queue, as shown below:
public Future<V> take() throws InterruptedException { return completionQueue.take();}
Implement invokeAny
As mentioned in section 77, the implementation of invokeAny of the AbstractExecutorService uses ExecutorCompletionService. The basic idea is to obtain the result through the take method after submitting the task, after obtaining the first valid result, cancel all other tasks. However, its implementation is optimized and complicated. Let's look at a simulated example to query a keyword from multiple search engines, but you only need one result. The simulation code is as follows:
Public class InvokeAnyDemo {static class SearchTask implements Callable <String> {private String engine; private String keyword; public SearchTask (String engine, String keyword) {this. engine = engine; this. keyword = keyword;} @ Override public String call () throws Exception {// simulate the Thread from the given engine search result. sleep (engine. hashCode () % 1000); return "<result for>" + keyword;} public static String search (L Ist <String> engines, String keyword) throws InterruptedException {ExecutorService executor = Executors. newFixedThreadPool (10); CompletionService <String> cs = new ExecutorCompletionService <> (executor); List <Future <String> futures = new ArrayList <Future <String> (engines. size (); String result = null; try {for (String engine: engines) {futures. add (cs. submit (new SearchTask (engine, keyword )));} For (int I = 0; I <engines. size (); I ++) {try {result = cs. take (). get (); if (result! = Null) {break ;}} catch (ExecutionException ignore) {// an exception occurred. The result is invalid. Continue }}} finally {// cancel all tasks. For completed tasks, canceling for (Future <String> f: futures) f. cancel (true); executor. shutdown ();} return result;} public static void main (String [] args) throws InterruptedException {List <String> engines = Arrays. asList (new String [] {"www.baidu.com", "www.sogou.com", "www.so.com", "www.google.com"}); System. out. println (search (engines, "programming "));}}
SearchTask simulates the query results from a specified search engine. search uses CompletionService/ExecutorService to execute concurrent queries. After obtaining the first valid result, it cancels other tasks.
Summary
This section is relatively simple. It mainly introduces the usage and principle of CompletionService. It facilitates processing the results of multiple asynchronous tasks through an additional result queue.
In the next section, we will discuss a common requirement-scheduled task.
(As in other sections, all the code in this section is in the https://github.com/swiftma/program-logic)
----------------
For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.