JUC concurrent programming --- thread pool, parallel computing ForkJoin, asynchronous callback FutureTask and completable future

Posted by Arnerd on Fri, 07 Jan 2022 20:56:51 +0100


JUC notes

10. Callable review

Details: Multithreaded pickup

The difference between Callable and Runnable

  • 1. Can have return value
  • 2. Exceptions can be thrown
  • 3. Different methods, run()/ call()
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // new Thread(new Runnable()).start();
        // new Thread(new FutureTask<V>()). start();              Putting futuretask into thread is equivalent to putting Runnable into thread
        // new Thread(new FutureTask<V>( Callable )). start();    Callable into futuretask

        MyThread myThread = new MyThread();
        FutureTask<String> task = new FutureTask<>(myThread);
        new Thread(task,"A").start();
        new Thread(task,"B").start();

        String s = task.get();
        System.out.println(s);

    }
    static class MyThread implements Callable<String> {
        @Override
        public String call() {
            System.out.println("call()"); // Several call s will be printed
            return Thread.currentThread().getName()+"->call";
        }
    }
}

11. Thread pool

Pooling Technology: prepare some resources in advance, take them when necessary, and return them to me when they are used up.

Thread pool benefits:

  1. Reduce resource consumption
  2. Improve response speed
  3. Convenient management

Thread reuse, control the maximum concurrent number, manage threads

Key points: three methods, seven parameters and four rejection strategies

11.1 three methods

The three methods refer to the three methods for creating thread pools in the Executors tool class

  • Executors. Newsinglethreadexecution(): create a single thread pool
  • Executors. Newfixedthreadpool (int Num): create a thread pool with a fixed size of num
  • Executors.newCachedThreadPool(): create a scalable thread pool, which is strong in case of strength and weak in case of weakness
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//Single thread pool
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);// A thread pool with a fixed size of 5
//        ExecutorService threadPool = Executors.newCachedThreadPool();// Create a scalable thread pool

        try {
            for (int i = 0; i < 100; i++) {
                //Use execute to create a thread
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"created");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //Remember to turn it off when you run out
            threadPool.shutdown();
        }
    }
}

11.2 seven parameters

First analyze a wave of source code

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

You can see that the essence of these three methods is the ThreadPoolExecutor() called. The seven parameters of this method are very important

public ThreadPoolExecutor(int corePoolSize,		// Core thread pool size
                          int maximumPoolSize, 	// Maximum core thread pool size
                          long keepAliveTime,	// After a timeout, it will be released if no one calls it
                          TimeUnit unit,		// Timeout unit: SECOND, MINUTE
                          BlockingQueue<Runnable> workQueue,	//Blocking queue
                          ThreadFactory threadFactory,			// Thread factory: it is used to create threads. Generally, it does not need to be moved
                          RejectedExecutionHandler handler) {	//Reject policy: 4 reject policies
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

As shown in the figure: image description of the above parameters

Attention! Attention!!!

Alibaba's development manual specifies that thread pools cannot be created using the three methods of Executors, but should be created using the original method of ThreadPoolExecutor

Big disadvantage: as shown in the figure, it will cause OOM

11.3 four rejection strategies

  • new ThreadPoolExecutor.AbortPolicy() / / when the bank is full, someone else comes in. If this person is not handled, an exception is thrown
  • new ThreadPoolExecutor.CallerRunsPolicy() / / where you come from, where you go!
  • new ThreadPoolExecutor.DiscardPolicy() / / if the queue is full, the task will be lost and no exception will be thrown!
  • new ThreadPoolExecutor.DiscardOldestPolicy() / / when the queue is full, try to compete with the earliest one without throwing an exception!

Now customize a thread pool:

public class ThreadPoolDemo02 {

    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS,
                                                                new ArrayBlockingQueue<>(3), 
                                                                Executors.defaultThreadFactory(), 
                                                                new ThreadPoolExecutor.DiscardPolicy());//The queue is full. Try to and
 The earliest competition will not throw exceptions!
        try {
            for (int i = 0; i < 9; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" used");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

}

11.4 how to determine the size of the pool

Judge IO intensive and CPU intensive (pool tuning)

  • 1. CPU intensive, several cores, that is, several cores, can maintain the highest CPU efficiency.
    • Setting formula: number of threads = number of CPU cores + 1 (hyper thread)
  • 2. IO intensive, judge the threads in your program that consume IO very much
  • Setting formula: number of threads = (1 + thread waiting time / thread CPU time) * number of CPU cores * CPU utilization
  • Or simple and crude: number of threads = 2 * number of CPU cores

Runtime.getRuntime().availableProcessors() gets the number of cores

public class ThreadPoolDemo02 {

    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());

        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                                                                Runtime.getRuntime().availableProcessors(),//Here, the maximum is set to the number of cores of the cpu
                                                                3, TimeUnit.SECONDS,
                                                                new ArrayBlockingQueue<>(3),
                                                                Executors.defaultThreadFactory(),
                                                                new ThreadPoolExecutor.DiscardPolicy());
        try {
            for (int i = 0; i < 9; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" used");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

}

12. Parallel computing ForkJoin

12.1 what is ForkJoin

ForkJoin appears in JDK 1.7 to execute tasks in parallel! So as to improve efficiency and deal with large amount of data

12.2 ForkJoin features

Job theft:

As shown in the figure above, ForkJoin maintains a double ended queue. After one queue is executed, you can steal tasks from other queues to assist in execution

12.3 ForkJoinTask and ForkJoinPool

ForkJoinTask

In fact, ForkJoinTask is a lightweight FutureTask

ForkJoinPool

ForkJoinPool is used to execute ForkJoinTask

Practice:

To use ForkJoin, we inherit an implementation class of ForkJoinTask and need to override the compute method

public class ForkJoinDemo extends RecursiveTask<Long> {
    @Override
    protected Long compute() {
        return null;
    }
}
/**
 * The task of summation calculation!
 * 3000 6000(ForkJoin) 9000(Stream Parallel stream)
 * // How to use forkjoin
 * // 1,forkjoinPool Execute through it
 * // 2,Calculate the task forkjoinpool execute(ForkJoinTask task)
 * // 3. The calculation class should inherit ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start; // 1
    private Long end; // 1990900000
    // critical value
    private Long temp = 10000L;
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if ((end - start) < temp) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else { // forkjoin recursion
            long middle = (start + end) / 2; // Intermediate value
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // Split the task and push the task into the thread queue
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork(); // Split the task and push the task into the thread queue
            return task1.join() + task2.join();
        }
    }
}
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();    //5509 is required for calculation
        test2();    //Calculation requires 3766    
        test3();    //The calculation requires 203 flow calculations yyds
    }


    // Ordinary programmer
    public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" Time:"+(end-start));
    }
    // Will use ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);// Submit task
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" Time:"+(end-start));
    }

    public static void test3(){
        long start = System.currentTimeMillis();
        // Stream parallel stream ()
        long sum = LongStream.rangeClosed(0L,
                10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"Time:"+(end-start));
    }
}

13. Asynchronous callbacks to Future and FutureTask

13.1 Future

Take a look at the jdk documentation

As an interface, Future cancels the execution results of specific Runnable or Callable tasks, queries whether they are completed, and obtains the results.

If necessary, you can get the execution result through the get method, which will block until the task returns the result.

The implementation classes include ForkJoinTask (mentioned above) and FutureTask (let's talk about FutureTask next)

13.2 FutureTask

To view the jdk documentation:

This class provides a basic implementation of Future, with methods to start and cancel calculation, query whether the calculation is complete, and retrieve the calculation results. The results can only be retrieved after the calculation is completed; If the calculation has not been completed, the get method will block it. Once the calculation is complete, you cannot restart or cancel the calculation (unless the calculation is called using runAndReset()).

FutureTask has three statuses:

  • Not started. FutureTask. Futuretask is not started before the run () method is executed. When creating a futuretask, futuretask has not been executed Futuretask was not started before the run () method.

  • Started. FutureTask. Futuretask is started while the run () method is being executed.

  • Completed. FutureTask. The execution of the run () method ends, or futuretask. Exe is called The cancel (...) method cancels the task or throws an exception during the execution of the task, which are called the completed state of futuretask.

Demo:

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask<String> task = new FutureTask<>(myThread);
        new Thread(task,"A").start();
        new Thread(task,"B").start();

        String s = task.get();
        System.out.println(s);

    }
    static class MyThread implements Callable<String> {
        @Override
        public String call() {
            System.out.println("call()"); // One call will be printed. The status of futureTask will be changed once it is executed. It cannot be executed again
            return Thread.currentThread().getName()+"->call";
        }
    }
}

13.3 CompletableFuture

When FutureTask obtains the execution results of asynchronous tasks, either call get to block or poll isDone to actively query whether it is completed. Both methods are not very good and will force the main thread to wait.

After completing a task phase, completable future can actively notify the next phase

Simple use:

  • runAsync: callback without return value
  • supplyAsync: callback with return value
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
    }

    public static void test1() throws ExecutionException, InterruptedException {
         //runAsync asynchronous callback with no return value
         CompletableFuture<Void> completableFuture =CompletableFuture.runAsync(()->{
             try {
                TimeUnit.SECONDS.sleep(2);
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
            System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
         });
         System.out.println("1111");
         completableFuture.get(); // Get blocking execution results
    }

    public static void test2() throws ExecutionException, InterruptedException {
        // supplyAsync asynchronous callback with return value
        // ajax, successful and failed callbacks
        // The error message is returned;
        CompletableFuture<Integer> completableFuture =
                CompletableFuture.supplyAsync(()->{
                    System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
                    int i = 10/0;
                    return 1024;
                });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // Normal return result
            System.out.println("u=>" + u); // Error message: Java util. concurrent. CompletionException: java. lang.ArithmeticException: / byzero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // You can get the return result of the error
        }).get());
    }
}

Topics: Java Multithreading JUC