Deep understanding of java thread pools

1. Why use thread pools? (Thread pool concept)

1. Thread pools are introduced to manage threads. The operating system needs to switch thread contexts frequently to affect performance.
2. Thread pools are actually pools of threads that help us reuse threads, avoid the overhead of creating a large number of threads, and improve response speed.

2. Is more threads better?

1. Threads are not as good as possible, because the creation of threads requires system memory. According to the jvm specification, a thread defaults to a maximum stack size of 1M. The more threads there are, the more memory it will consume.
2. If the thread creation time + destroy time > the task execution time, there is no need to create a thread.

3. Creation of Thread Pool

1. Thread pools recommend manual creation, typically created with ThreadPoolExecutor, which has four constructors

Let's first understand these parameters of the thread pool

  • corePoolSize: Maximum number of core threads in the thread pool
  • maximumPoolSize: Maximum thread pool size
  • keepAliveTime: The idle lifetime size of non-core threads in the thread pool
  • Unit: Thread idle lifetime unit
  • workQueue:Blocked queue holding task queue
  • threadFactory: The factory where threads are created. You can set meaningful names for the threads you create to view them easily
  • handler: Thread pool rejection policy, there are four types
    a, AbortPolicy (throw exception directly, default rejection policy)
    b. DiscardPolicy (Discard Tasks Directly)
    c, DiscardOldestPolicy (discards the oldest task in the queue and continues to submit the current task to the thread pool)
    d, CallerRunsPolicy (handed directly to the caller to handle the task)

Here's a demonstration

First, manually declare a core thread of 1, a maximum thread of 2, a thread blocking queue of 2, and the rejection policy is to directly discard the task. Then five tasks come in. When the first task comes, the thread with a core of 1 can handle it. When the second task comes, it can only be placed in the blocking queue and the third in the blocking queue. When the fourth task comes, the blocked queue is full, and a thread starts to reach the maximum thread to process it. When the fifth task comes, the thread pool cannot process it, and the task is discarded directly. Four tasks are executed at the end of the execution.

package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @Author ll
 * @Description
 * @Date 1/3/22 10:00 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ThreadPoolApplicationTest {

   @Test
    public void ThreadPoolExecutorTest(){

       ThreadFactory threadFactory = new ThreadFactory() {
           private AtomicInteger idx = new AtomicInteger(1);
           private static final String THREAD_NAME_PREFIX = "mythread-pool-";
           @Override
           public Thread newThread(Runnable r) {
               return new Thread(r, THREAD_NAME_PREFIX + idx.getAndIncrement());
           }
       };

       ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
               100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
               threadFactory, new ThreadPoolExecutor.DiscardPolicy());

       Runnable runnable = () -> {
           System.out.println("Current Task Executed,thread:" + Thread.currentThread().getName() + "Executing");
           try {
               // Wait 1s
               TimeUnit.SECONDS.sleep(1);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       };

       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);

       try {
           // Wait 10s
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

   }


}


Other situations are similar. You can try to write it.

2. You can also use the Thread Pool tool class to create a thread pool, but it is not recommended. As to why not, let's first look at the ways this Executors provides to create a thread pool.

You can see that = provides four ways to create different thread pools

  • newFiexedThreadPool(int Threads): Creates a pool of threads with a fixed number of threads.
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:36 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class FixedThreadPoolTest {

    @Test
    public void fixedThreadPoolTest(){
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Runnable runnable = () -> {
            System.out.println("thread:" + Thread.currentThread().getName() + "Executing");
        };
        for(int i = 0; i < 5; i++){
            executorService.submit(runnable);
        }
		executorService.shutdown();
    }

}

  • newSingleThreadExecutor(): Create a single-threaded Executor.
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:44 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class SingleThreadExecutorTest {

    @Test
    public void singleThreadExecutorTest(){
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for(int i = 0; i < 5; i++){
            executorService.submit(() -> {
                System.out.println("thread:" + Thread.currentThread().getName() + "Executing");
            });
        }
        executorService.shutdown();
    }
}

  • newCachedThreadPool(): Creates a cacheable thread pool, calls execute to reuse previously constructed threads (if available), creates a new thread if none is available, adds it to the pool, and removes those threads from the cache that have not been used for more than 60 seconds.
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:57 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class CachedThreadPoolTest {

    @Test
    public void cachedThreadPoolTest(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++){
            executorService.submit(()->{
                System.out.println("thread:" + Thread.currentThread().getName() + "Executing");
            });
        }
        executorService.shutdown();
    }
}

  • newScheduledThreadPool(int corePoolSize): Creates a thread pool that supports timed and periodic task execution and in most cases replaces the Timer class.
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author ll
 * @Description
 * @Date 1/4/22 12:00 AM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ScheduledThreadPoolTest {

    @Test
    public void scheduledThreadPoolTest() {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
        for(int i = 0; i < 5; i++){
            scheduledThreadPoolExecutor.schedule(() -> {
                System.out.println("thread:" + Thread.currentThread().getName() + "Executing" + DateUtil.now());
            }, 1, TimeUnit.SECONDS);
        }
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task Execution Completed");
        scheduledThreadPoolExecutor.shutdown();
    }
}

3. The following explains why creating threads directly using Executors is not recommended

  • The source code for newFiexedThreadPool(int Threads) shows that the blocked queue has no size set and the default is Integer.MAX_VALUE, which can be considered unbounded, may eventually lead to OOM.
  • The newSingleThreadExecutor() source code shows that there is only one thread in the thread pool, and the blocked queue does not have a specified size. By default, it is also Integer.MAX_VALUE, which can be considered unbounded, may eventually lead to OOM.
  • The newCachedThreadPool() source code shows that the core thread here is 0 and the maximum thread is Integer.MAX_VALUE, which can be thought of as unbounded, has a blocked queue with no elements stored, that is, submitted directly to threads without holding them, possibly creating an infinite number of threads, which could eventually lead to OOM.
  • The newScheduledThreadPool(int corePoolSize) source code can see that similar to the above, DelayWorkQueue: An unbounded blocking queue implemented with a priority queue that can extract elements only when the delay expires, but with a maximum thread of Integer.MAX_VALUE, which can be thought of as unbounded, may lead to OOM.

    The thread pool workshops used by the above four Executors to create thread pools are also default and cannot specify a deny policy. All use the default deny policy, AbortPolicy

Here's a casual demonstration of an OOM

package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 10:00 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ThreadPoolApplicationTest {

    @Test
    public void test(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        int i = 0;
        while (true) {
            executorService.submit(new MyTask(i++));
        }

    }

    class MyTask implements Runnable{

        private int i;

        public MyTask(int i){
            this.i = i;
        }

        @Override
        public void run() {
            System.out.println(i);
        }
    }
}

The results are as follows

4. Processing task priority in thread pool

First, determine if the core thread is full, the core thread processes when it is not full, the core thread creates a blocked queue and puts it in the blocked queue when it is not full, the blocked queue is full to see if the maximum number of threads is full, the maximum number of threads is not full to create thread processes, and the maximum number of threads is created to execute according to their own defined rejection policy.

5. Configuration of Thread Pool Cores

Typically, you configure the thread pool size based on the type of task:
For CPU intensive tasks, the reference value can be set to Number of Cores + 1
For IO-intensive tasks, the reference value can be set to 2*cores
Java Gets Number of Thread Pool Cores

System.out.println(Runtime.getRuntime().availableProcessors());

Reference Links
https://juejin.cn/post/6844903889678893063
https://juejin.cn/post/6844903560623161352
https://blog.csdn.net/xiaojin21cen/article/details/87269126
https://developer.huawei.com/consumer/cn/forum/topic/0202438478344770295

Keywords: Java Back-end thread pool

Added by robert.access on Tue, 04 Jan 2022 09:42:29 +0200