Send you a strange technique of concurrent programming to take off comfortably

  1. home page
  2. special column
  3. java
  4. Article details
0

Send you a strange technique of concurrent programming to take off comfortably

Yan Yan Published just now

Write in front

Time flies. I'm going to comb all my past articles.

I have to say, this is really a very grand project.

In the process of sorting out, I found that many early works were written, and the amount of reading was very hip pulling.

In the process of sorting out the article, I found that there are many knowledge points I have written before, which I may not remember clearly.

Shame, shame.

But this is really a process of learning and consolidation again, which is very good.

Talk about Future

This article will talk about CompletionService.

Before we talk about it, let's review the usage of Future.

Let me ask you first. After you submit a set of computing tasks to the thread pool, you want to get the return value.

What submission method should you use for Executor? What overload type of this submission method?

what? You can't answer? Bah, you scum man, forgot after whoring last week?

There is an article that says:

Because it is a group of computing tasks, you want to get the return value to do things. The return value is encapsulated in Future.

How to get it?

Call the get method of Future, including the licking dog type get with unlimited waiting without timeout, and the slag man type get with timeout and give up at the point:

Let's take a look at an example:

public class JDKThreadPoolExecutorTest {

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ArrayList<Future<Integer>> list = new ArrayList<>();
        Future<Integer> future_15 = executorService.submit(() -> {
            TimeUnit.SECONDS.sleep(15);
            System.out.println("The execution time is 15 minutes s The execution of is complete.");
            return 15;
        });
        list.add(future_15);
        
        Future<Integer> future_5 = executorService.submit(() -> {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("The execution time is 5 minutes s The execution of is complete.");
            return 5;
        });
        list.add(future_5);
        
        Future<Integer> future_10 = executorService.submit(() -> {
            TimeUnit.SECONDS.sleep(10);
            System.out.println("The execution time is 10 minutes s The execution of is complete.");
            return 10;
        });
        list.add(future_10);
        
        System.out.println("Start preparing for results");
        for (Future<Integer> future : list) {
            System.out.println("future.get() = " + future.get());
        }
        Thread.currentThread().join();
    }
}

Now there are three tasks, the execution time is 15s/10s/5s respectively. These three Callable tasks are submitted through the submit method of the JDK thread pool.

You compile it with your eyes and output it in your heart. What do you think is the output result of this code.

First, the main thread submits the three tasks to the thread pool, stores the corresponding returned Future in the List, and then executes the output statement of "start preparing to obtain results".

Then enter the for loop, execute the future.get() operation in the loop, and block the wait.

See if the output you want is like this:

From this output, we can see the problem. Obvious barrel effect.

Among the three asynchronous tasks, the one that takes the longest time is executed first, so it enters the list first. Therefore, when the task result is obtained in the loop, the get operation will be blocked all the time, even if the task with an execution time of 5s/10s has been executed.

OK, for example. Imagine a scenario:

Suppose you are a sea king, you have many ordinary female friends. You invited three female friends to dinner at the same time. Say to them: you make up first. Well, tell me, I'll drive to pick you up.

Xiaohong takes 2 hours to make up. Floret make-up takes 1 hour. Xiaoyuan takes 30 minutes to make up.

Because you first told Xiaohong, you have been waiting at Xiaohong's door for Xiaohong to finish her makeup. When Xiaohong finished her makeup, you received it in the car. The other two friends were already ready and waiting for you to pick her up at home.

This is not what a qualified sea king should look like.

This is the limitation of future in this scenario.

According to the above scenario codes, you can get them (the codes can be directly copied and pasted. I suggest you take them out and run them):

public class JDKThreadPoolExecutorTest {

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ArrayList<Future<String>> list = new ArrayList<>();
        System.out.println("Let's have dinner with some girls.");
        Future<String> future_15 = executorService.submit(() -> {
            System.out.println("Xiao Hong: OK, brother. It takes me two hours to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(15);
            System.out.println("Xiao Hong: I'll be on time in two hours. Brother, come and pick me up.");
            return "The little red is over.";
        });
        list.add(future_15);
        Future<String> future_5 = executorService.submit(() -> {
            System.out.println("Xiaoyuan: OK, brother. It takes me 30 minutes to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(5);
            System.out.println("Xiaoyuan: I'll be on time in 30 minutes. Brother, come and pick me up.");
            return "Xiaoyuan is finished.";
        });
        list.add(future_5);

        Future<String> future_10 = executorService.submit(() -> {
            System.out.println("Xiaohua: OK, brother. It takes me an hour to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(10);
            System.out.println("Xiaohua: I'll be on time in an hour. Brother, come and pick me up.");
            return "The flowers are finished.";
        });
        list.add(future_10);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("All notified,Wait.");
          for (Future<String> future : list) {
            System.out.println(future.get()+"I'll pick her up.");
        }
        Thread.currentThread().join();
    }
}

The output results are as follows:

It's said that they are all the same ordinary friends. Why do you have to wait for Xiaohong with the longest makeup time? Why not pick up the first one who moves fast?

What do you think of Xiaoyuan and Xiaohua when you operate like this? Can only say: you are a good man.

what? You central air conditioner asked me "what is sea king"?

CompletionService save Sea King

The above scenario is different when we introduce CompletionService.

Let's look directly at the usage:

ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);

It is very convenient to use. You only need to wrap the thread pool with ExecutorCompletionService.

Then, when submitting the task, use the submit method of the competitionService. The code is as follows:

public class ExecutorCompletionServiceTest {

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ExecutorCompletionService<String> completionService =
                new ExecutorCompletionService<>(executorService);
        System.out.println("Let's have dinner with some girls.");
        completionService.submit(() -> {
            System.out.println("Xiao Hong: OK, brother. It takes me two hours to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(15);
            System.out.println("Xiao Hong: I'll be on time in two hours. Brother, come and pick me up.");
            return "The little red is over.";
        });
        completionService.submit(() -> {
            System.out.println("Xiaoyuan: OK, brother. It takes me 30 minutes to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(5);
            System.out.println("Xiaoyuan: I'll be on time in 30 minutes. Brother, come and pick me up.");
            return "Xiaoyuan is finished.";
        });
        completionService.submit(() -> {
            System.out.println("Xiaohua: OK, brother. It takes me an hour to make up. Wait a minute.");
            TimeUnit.SECONDS.sleep(10);
            System.out.println("Xiaohua: I'll be on time in an hour. Brother, come and pick me up.");
            return "The flowers are finished.";
        });
        TimeUnit.SECONDS.sleep(1);
        System.out.println("All notified,Wait.");
        //Loop 3 times because the above three asynchronous tasks are submitted
        for (int i = 0; i < 3; i++) {
            String returnStr = completionService.take().get();
            System.out.println(returnStr + "I'll pick her up");
        }
        Thread.currentThread().join();
    }
}

You compile it in your eyes and output it in your heart

Forget it, don't compile it. Just show you the results. I can't wait:

Whoever puts on makeup first will pick up first.

Writing here, I couldn't help clapping my hands when I saw the output result.

The real sea king should be a master of time management.

First, compare the output results, all stand up and applaud:

Then compare the differences between the two versions:

Little or even little change.

The object that executes the submit method becomes ExecutorCompletionService.

The method of obtaining task results becomes:

String returnStr = completionService.take().get();

Let's not look at the principle. You will carefully sample this method to obtain the results.

completionService.take() what came out, then called the get method.

According to this get, my intuition tells me that what I take must be a future object. The future object must be placed in a queue.

In the next section, I'll take you to confirm it.

CompletionService principle

First, CompletionService is an interface:

ExecutorCompletionService is the implementation class of this interface:

Take a look at the construction method of ExecutorCompletionService:

You can see that a thread pool object needs to be passed in. The default queue is LinkedBlockingQueue.

Of course, we can also specify which queue to use:

Then take a look at its task submission method:

Because the ExecutorCompletionService is mainly used to process the return value gracefully. Therefore, it supports two types of submit, both of which have return values.

The above time management master version of Sea King uses the Callable method.

Let's compare the difference between Executor direct submission and ExecutorCompletionService submission:

The difference lies in the execute method.

The ExecutorCompletionService submits tasks as follows:

executor.execute(new QueueingFuture(f));

The difference lies in the Runable in the execute method:

Take a look at what queueing future is:

The secret is basically in this.

QueueingFuture inherits from FutureTask. Override the done method and put the task in the queue.

The meaning of this method is that when the task is completed, it will be put into the queue. In other words, all the tasks in the queue are done tasks, and this task is the future.

If the task method of the queue is called, the wait is blocked. When you wait for the future that must be ready, you can call get to get the result immediately.

What do you think this operation is doing?

Isn't that what you're doing?

Before you submit a task, you also need to directly care about the future returned by each task. Now CompletionService helps you track these future.

The decoupling between the caller and future is completed.

After the principle analysis, say a place that needs attention.

When your usage scenario doesn't care about the return value, don't use CompletionService to submit tasks.

Why?

Because as I said earlier, there is a queue in it. When you don't care about the return value, you won't process the queue, resulting in more and more objects in the queue.

Finally, it blew up and OOM.

Application in open source framework

As mentioned earlier, CompletionService is an interface. In addition, the ExecutorCompletionService of JDK implements this interface.

There are also corresponding implementations in the open source framework. For example, Redisson:

You see this implementation as like as two peas in ExecutorCompletionService, but there are some differences in implementation.

When it puts the future on the queue, it does not override the done method, but uses the onComplete of responsive programming:

The core idea of CompletionService is: Executor plus Queue.

This idea reminds me of a class I saw in Dubbo:

The core logic in the doInvoker method of this class is as follows:

First, a queue is defined where the label is ①.

The asynchronous task is submitted in the loop body where the label is ②. It takes several service providers to have several cycles.

The sub thread puts the returned result into the queue at the place labeled ③.

As soon as it is put in, it can be obtained at the place marked ④ (within the specified time), and then the program returns immediately.

In this way, we can call multiple service providers in parallel and return immediately as long as one service provider returns.

I think this idea has something in common with the idea of CompletionService.

We should learn from CompletionService and its ideas.

Reading 5 was published just now
Like collection
7 prestige
0 fans
Focus on the author
Submit comments
You know what?

Register login
7 prestige
0 fans
Focus on the author
Article catalog
follow
Billboard

Tags: Java Concurrent Programming

Posted on Sat, 30 Oct 2021 05:57:50 -0400 by ClaytonBellmor