Brain figure gives an overview of

CPU intensive tasks and IO intensive tasks

Before we talk about Future and Fork/Join, I want to talk about the tasks we’re running. In general, the tasks we run fall into two categories at the CPU level: CPU-intensive and IO intensive.

cpu-intensive

Cpu-intensive tasks are also called computationally intensive tasks. This means that the hard disk and memory perform much better than the CPU. Most of the time, CPU Loading is 100%. It takes a short time for the CPU to read and write disk memory, but it takes a long time for the CPU to compute data.

CPU bound programs that spend the vast majority of their time doing calculations, logical decisions, and other CPU actions are called CPU bound. Such as calculating PI thousands of thousands of digits, video hd decoding, or can be simply understood as an algorithm-centric program that involves a large number of complex algorithmic operations.

Cpu-bound programs tend to have high CPU usage, and program I/O takes very little time.

For CPU-intensive applications, the recommended number of threads is:

Threads = Number of CPU cores + 1 (hyperthreading)

Why would you do that?

Cpu-intensive tasks consume CPU resources and depend on the COMPUTING capability of the CPU.

While computationally intensive tasks can also be accomplished by multitasking, creating too many threads can lead to frequent thread switching

As we all know, for Java, the kernel threading model, the more time it takes to switch between kernel mode and user mode, the lower the CPU computing efficiency. Therefore, in order to avoid unnecessary time loss caused by thread switching, the number of threads equals the number of CPU cores for CPU-intensive tasks.

Why do WE have plus 1? One is that many cpus today support hyperthreading. The second is that a thread may block or hang for some reason. Of course, the thread switching that this extra thread might cause also needs to be measured in the actual application.

Remember that technology is dead, thinking is alive, live learning and application, there is no best solution, only the most suitable solution

IO intensive

I believe you can see the above CPU intensive basic can infer the meaning of IO intensive.

IO – intensive means that CPU performance is much better than reading and writing data from hard drives and memory. Most of the time is spent on IO – bound. Our common Web application network request, file reading, database CRUD and other operations are IO intensive.

The characteristic of these programs is that the CPU Loading is not very high when the general performance reaches its limit. This may be because the task itself has a lot of IO operations, and the business logic pipeline (pipeline can be simply understood as a process flow) does not do a very good job of utilizing the processor’s power.

Here’s a rule of thumb for IO intensive programs:

Number of threads = (1 + Thread waiting time/thread CPU time) x NUMBER of CPU cores x CPU usage

Or the rough-and-ready way. Assuming that the ratio of thread wait time to computation time is 1 and CPU utilization is 100%, we have the following formula:

Number of threads = 2 x number of CPU cores

Why do this:

IO intensive tasks require us to make full use of our CPU so we need more threads to use our CPU to handle more tasks

How to understand the thread wait time/thread CPU usage time calculation. It means that the longer our threads wait the longer our CPU is free so we need more threads to take advantage of the CPU free resources. The longer a thread spends on CPU computation, the less time the CPU is idle, and we don’t need more threads to occupy resources, potentially self-defeating the overhead of thread switching

Whether it’s 2 * CPU cores in real development, or a fine-grained estimate, is the time to evaluate your business requirements.

Fork Join Framework (CPU intensive task processing)

With that in mind, the ForkJoin framework (available in Java 1.7) and ForkJoinPool, as the title suggests, are suitable for CPU intensive tasks. Its core idea is divide and conquer, divide and conquer.

Fork, Fork, Fork, Fork It can be interpreted as dividing big tasks into smaller ones. Join: Join Think of it as combining small tasks.

Digression: This feels like a map/reduce idea. Since MapReduce is not the focus of this article, I will not talk about it in depth. I will write a separate article about it someday. Hey hey.

For a simple example, if I want to add up 100,000 numbers, I can think about breaking up 10 subtasks, adding 10,000 numbers to each task and adding them up, and then my calculation efficiency increases 10 times.

I have to say the Future interface and its implementation of the FutureTask class

Submit (); AbstractExecutorService submit(); they all return values, Future.

    publicFuture<? > submit(Runnable task) {if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
Copy the code

Whether you pass in a Runnable or a Callable is returned as RunnableFuture wrapped in newTaskFor(). Take another look at the newTaskFor() method

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
Copy the code

You end up with a new FutureTask class, so you’re actually returning FutureTask.

A feasible bloated task optimization idea for asynchronous blocking:

3. FutureTask receives the task. 4. FutureTask gets the result by blocking the get() method of FutureTask at an appropriate time in the main thread. 5 Summarize your results

Talk is cheap Show me the Code

    @Test
    public void testFutureTask(a) throws ExecutionException, InterruptedException, TimeoutException {
     
 		// Get the number of available CPU cores
        int cpuNum = Runtime.getRuntime().availableProcessors();
        System.out.println("The number of available CPU cores in the current system is:"+cpuNum);
        // Create a thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(cpuNum);
        // Create task, split 300000 autoadd task into 3
        FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName());
            int sum=0;
            for (int i=0; i<=100; i++){ Thread.sleep(1);
                sum+=i;
            }
            return sum;
        });
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName());
            int sum=0;
            for (int i=101; i<=200; i++){ Thread.sleep(1);
                sum+=i;
            }
            return sum;
        });
        FutureTask<Integer> futureTask3 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName());
            int sum=0;
            for (int i=201; i<=300; i++){ Thread.sleep(1);
                sum+=i;
            }
            return sum;
        });

        // Tasks are submitted for parallel execution by threads in the thread pool
        executorService.submit(futureTask1);
        executorService.submit(futureTask2);
        executorService.submit(futureTask3);

        // The main thread can do whatever it wants
        //1. Have a smoke
        / / 2. Drink wine
        //3. Perm my hair
        //4. Thread. Sleep ();

        // If the task is completed, I will check whether the task is completed
 		// The main thread blocks to get the result
        int result1 = futureTask1.get(3, TimeUnit.SECONDS);
        int result2 = futureTask2.get(3, TimeUnit.SECONDS);
        int result3 = futureTask3.get(3, TimeUnit.SECONDS);
        / / 45150
        System.out.println("The correct answer is: 45150+(result1+result2+result3));

Copy the code

Single thread comparison method:

 @Test
    public void sum30w(a) throws InterruptedException {
        long sum=0;
        for (int i=0; i<=300; i++){ Thread.sleep(1);
            sum+=i;
        }
        System.out.println("The correct answer is: 45150+sum);
    }
Copy the code

From the running results, multi-threaded FutureTask can enable me to complete the entire task in 300 ms, but the single thread is far more than 300 ms

Here to note when considering to optimize your code If you logic of the code itself is very simple, the running speed is quickly, there is no need to partition, because itself using a thread in the process of thread creation and summary of performance must be consumed, may be the last you also run more slowly after partition multithreading optimization, we often call this the avoid excessive optimization

Meet ForkJoinPool

  1. ForkJoinPool is a complement to ExecuterService

ForkJoinPool is mainly used to implement divide-and-conquer algorithms. ForkJoinPool is suitable for computation-intensive tasks.

conclusion

OK, I am satisfied with the length of the article, it is long enough. ForkJoinPool usage and FutureTask source-level logic will be discussed later in this article.

If you like this article, please click a like, add a favorite, add a follow, if you can talk about your opinion, even better.

Let the lights dim where we can meet, I am nonsense, I am stranded, we next article goodbye ~

Audio version will be provided in the public account!! Welcome to: