Ali was asked about the Java ThreadPool thread pool on both sides. After reading this article, he angered the interviewer

Advantages of thread pool

The work done by the thread pool is mainly to control the number of running threads, put tasks into the queue during processing, and then start these tasks after threads are created. If the number of threads exceeds the maximum number, the exceeded threads queue up, wait for other threads to complete execution, and then take tasks out of the queue for execution

Characteristics of thread pool

Thread reuse, control the maximum number of concurrent threads, and manage threads

  • Reduce resource consumption. Reuse the created threads to reduce the overhead of creating and destroying threads

  • Improve response speed. When the task arrives, the task can be executed immediately without waiting for the thread to be created

  • Improve thread manageability. Thread pool can be used to uniformly allocate, tune and monitor threads

1 method of thread pool

  • Good performance in executing long-term tasks. Create a thread pool with N fixed threads, which can control the maximum concurrent number of threads, and a thread pool with a fixed number of threads
ExecutorService threadPool = Executors.newFixedThreadPool(N);
  • When a single task is executed, it will only use a single worker thread, one pool and one thread
ExecutorService threadPool = Executors.newSingleThreadExecutor();
  • When executing short-term asynchronous tasks, the thread pool can be cached. The thread pool can create new threads as needed, but the previously constructed threads can be reused, and idle threads can be recycled flexibly. The pool can be expanded
ExecutorService threadPool = Executors.newCachedThreadPool();
  • Periodic thread pool; Support regular and periodic task execution
ExecutorService threadPool = Executors.newScheduledThreadPool();

(1) newFixedThreadPool

Thread pool that can control the maximum number of concurrent threads:

public class FixedThreadPool {

    private static AtomicInteger num = new AtomicInteger(0);

    private static ExecutorService executorService = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        countSum c= new countSum();
        //submit coutSum as a Task to the thread pool
        for (int i = 0; i < 2; i++) {
            executorService.submit(c);
        }
        //Close after Task execution
        executorService.shutdown();
    }

    static class countSum implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 500; i++) {
                try{
                    System.out.println("Thread - "+Thread.currentThread().getName()+" count= "+ num.getAndIncrement());
                    Thread.sleep(100);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

result:

(2) newSingleThreadExecutor

Thread pool that only uses a unique worker thread to execute tasks:

public class SingleThreadExecutor {

    private static AtomicInteger num = new AtomicInteger(0);

    private static ExecutorService executorService = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {
        //submit coutSum as a Task to the thread pool
        for (int i = 0; i < 2; i++) {
            executorService.submit(new countSum());
        }
        //Close after Task execution
        executorService.shutdown();
    }

    static class countSum implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 500; i++) {
                try{
                    System.out.println("Thread - "+Thread.currentThread().getName()+" count= "+ num.getAndIncrement());
                    Thread.sleep(100);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

result:

(3) newScheduledThreadPool

The passed parameter value is the size of corePoolSize, which supports regular and periodic task execution

Example of deferred execution: call the schedule method with three parameters: Task, Delay and TimeUnit

public class ScheduledThreadPool {
    // corePoolSize = 2
    private static ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

    public static void main(String[] args) {
        System.out.println("Thread - "+Thread.currentThread().getName()+" BEGIN "+ new Date());

        service.schedule(new print(),5, TimeUnit.SECONDS);

        service.shutdown();
    }

    static class print implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try{
                    System.out.println("Thread - "+Thread.currentThread().getName()+" Delay 5 second and sleep 2 second "+ new Date());
                    Thread.sleep(2000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

result:

Timed execution example: call scheduleAtFixedRate method with four parameters: Task, initialDelay, Period and TimeUnit

public class ScheduledThreadPool {
    // corePoolSize = 1
    private static ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) {

        System.out.println("Thread - "+Thread.currentThread().getName()+" BEGIN "+ new Date());

        service.scheduleAtFixedRate(new print(),5,3,TimeUnit.SECONDS);
    }

    static class print implements Runnable{
        @Override
        public void run() {
            System.out.println("Thread - "+Thread.currentThread().getName()+" Delay 5 second and period 3 second "+ new Date());
        }
    }
}

result:

(4) newCachedThreadPool

The thread pool can be cached. If the length of the thread pool exceeds the processing needs, the idle threads will be recycled. If there is no recyclable thread, a new thread will be created. That is, if the previous task has been completed, the thread will be reused:

public class CachedThreadPool {

    private static AtomicInteger num = new AtomicInteger(0);

    private static ExecutorService service = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        countSum c = new countSum();
        for (int i = 0; i < 3; i++) {
            try {
                service.submit(c);
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        service.shutdown();
    }

    static class countSum implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("Thread - "+Thread.currentThread().getName()+" countSum= "+num.getAndIncrement());
            }
        }
    }
}

Result: thread Sleep (1000), that is, sleep for one second. After the last task is completed, the thread can be reused without creating a new thread

If the tree Note out sleep (1000) and you will find three threads running

If you are interested, you can learn about their underlying source code. For CachedThreadPool, the maximum number of new threads can be integer MAXIMUM

2. Underlying principle of thread pool

Take newFixedThreadPool as an example

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
    }

Seven parameters of thread pool

  • corePoolSize: the number of resident core threads in the thread pool
  • maximumPoolSize: the maximum number of threads that can be executed simultaneously in the thread pool, which must be greater than 1
  • keepAliveTime: the survival time of redundant idle threads; When the number of threads in the current pool exceeds the corePoolSize, the redundant threads will be destroyed when the idle time reaches keepAliveTime
  • Unit: the unit of keepAliveTime
  • workQueue: task queue, a task submitted but not yet executed
  • threadFactory: refers to the thread factory that generates the working threads in the thread pool. It is used to create threads. It is generally the default
  • handler: reject policy, which indicates how to reject the runnable request execution when the queue is full and the number of worker threads is greater than or equal to the maximum number of threads in the thread pool

Four processes of thread pool

1) After creating the thread pool, start waiting for requests

2) When calling the execute() method to add a request task, the thread pool will make the following judgment:

  • If the number of running threads is less than corePoolSize, create a thread to execute the task immediately
  • If the number of running threads is greater than or equal to corePoolSize, put the task into the waiting queue
  • If the waiting queue is full, but the number of running threads is less than max, create a non core thread to execute the task
  • If the queue is full and the number of running threads is greater than max, the thread pool will start the saturation rejection policy

3) When a thread completes a task, it will remove a task from the waiting queue for execution

4) When the idle thread exceeds the time defined by keepAliveTime, it will judge:

  • If the current running thread is larger than corePoolSize, the thread is destroyed
  • After all threads finish executing tasks, the number of threads will be restored to the size of corePoolSize

3 thread pool strategy and analysis

Note: Alibaba JAVA Development Manual: thread pools do not allow Executors to create thread pools, but customize thread pools by using ThreadPoolExecutor to avoid the risk of resource depletion

Disadvantages of thread pool object returned by Executors:

1) FixedThreadPool and SingleThreadPool:

The allowed request queue length is integer MAX_ Value, which may accumulate a large number of requests, resulting in OOM

2) CachedThreadPool and ScheduledThreadPool:

The number of threads allowed to be created is integer MAX_ Value, a large number of threads may be created, resulting in OOM

Reject policy

1)AbortPolicy

Direct throw
RejectedExecutionException exception prevents the system from running normally

2)CallerRunsPolicy

The "caller run" adjustment mechanism, which neither discards tasks nor throws exceptions, but backs some tasks back to the caller, so as to reduce the traffic of new tasks

3)DiscardPolicy

This policy discards the tasks that cannot be processed, and does not handle them or throw exceptions. This is the best strategy if tasks are allowed to be lost

4)DiscardOldestPolicy

Discard the longest waiting task in the queue, then add the current task to the queue and try to submit the current task again

How to set the maximumPoolSize size

Runtime. getRuntime(). The availableprocessors() method gets the number of cores

CPU intensive

maximumPoolSize is set to the number of cores + 1

IO intensive

maximumPoolSize is set to the number of cores / blocking coefficient

WeChat search "programmer black brother" attention to the official account, daily brush, easy to upgrade technology, and capture all kinds of offer:

Keywords: Java Interview Multithreading queue Concurrent Programming

Added by sectachrome on Thu, 23 Dec 2021 23:05:23 +0200