Thread pools for pooling technology

What is pooling technology? To put it simply, it is to optimize the use of resources. I have prepared some resources, and when someone wants to use them, they can come to me and take them back to me. One of the more important implementations is thread pooling. So what are the benefits of using pooling techniques for thread pools?

  • Reduce resource consumption
  • Increase the speed of response
  • Convenient management

So there are five implementations of thread reuse, you can control the maximum number of concurrent threads, you can manage thread ### thread pools and actually I prefer to call them four encapsulated implementations, one primitive implementation. These four encapsulated implementations all rely on the original implementation. So here, we first introduce the realization of the four types of encapsulation way # # # # # (1) Executors. NewSingleThreadExecutor () the thread pool is very interesting, said it was a thread pool, but there is only one thread pool. If a thread is stopped because of an exception, a new thread is automatically created to replenish it. We can test this: we run ten print tasks on the thread pool and see that they all use the same thread

    public static void test01() { ExecutorService threadPool = Executors.newSingleThreadExecutor(); Try {// Perform ten print tasks on the threadfor(int i = 1; i <= 10; i++){
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"=> Execution complete!"); }); } } catch (Exception e) { e.printStackTrace(); } finally {// ThreadPool.shutdown (); }}Copy the code
Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required.Copy the code

(2) the Executors. NewFixedThreadPool (specified number of threads)

This thread pool can specify the size of our thread pool, which can be allocated to our specific business and situation. It creates a thread pool with the same number of core threads as the maximum number of threads, so the number of threads in the pool will neither increase nor decrease. If there are idle threads, the task will be executed. If there are no idle threads, the task will be queued and wait for idle threads. Let’s also test:

    public static void test02() { ExecutorService threadPool = Executors.newFixedThreadPool(5); Try {// Perform ten print tasks on the threadfor(int i = 1; i <= 10; i++){
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"=> Execution complete!"); }); } } catch (Exception e) { e.printStackTrace(); } finally {// ThreadPool.shutdown (); }}Copy the code

We created a thread pool of five threads, and when we print the task, we can see that all the threads are working

Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required. Pool-1-thread-3 => No further action is required. Pool-1-thread-5 => No further action is required. Pool-1-thread-4 => No further action is required.Copy the code

(3) the Executors. NewCachedThreadPool ()

MAX_VALUE, i.e., there is no limit on the number of threads in the thread pool. However, if there are idle threads that can be reused, they will be used preferentially. If there are no idle threads, new threads will be created to process the task and put into the thread pool. Let’s test that out as well

(4) the Executors. NewScheduledThreadPool (specify the maximum number of threads)

Create a there is no limit to the maximum number of threads to regular the execution thread pool Here, and create a only a single thread can perform regularly thread pool (Executors. NewSingleThreadScheduledExecutor ()) these are all of the above spread thread pool, was introduced in detail. We also mentioned that there are five ways to implement a thread pool, but we’ve actually covered only four. So what’s the last one? No hurry, we can click on our thread pool implementation above the source code to view, you can find

  • NewSingleThreadExecutor () implementation source code

Clicking on several other thread pools eventually reveals that they are actually using this same ThreadPoolExecutor. Let’s paste the source code and analyze it, which is basically these seven parameters

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use forholding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code  execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentExceptionif one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        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;
    }
Copy the code

Unsurprisingly, this is the last approach and the basis for other implementations. This is also the easiest way to control because we can set the parameters freely. It is also mentioned in the Alibaba development manual

ThreadPoolExecutor

  • CorePoolSize: specifies the number of core threads in the thread pool
  • WorkQueue: A blocking queue that holds tasks waiting to be executed
  • MaximunPoolSize: specifies the maximum number of threads in a thread pool
  • ThreadFactory: Factory for creating threads
  • RejectedExecutionHandler: the queue is full, and achieve maximum number of threads, thread processing strategy for a new task
  • KeeyAliveTime: indicates the lifetime of idle threads
  • TimeUnit: indicates the unit of survival time

There are also two ways to set the maximum number of threads in the thread pool

  1. CPU intensive Obtain the number of CPU cores, different hardware is different, set the number of cores the number of threads. We can do it in codeRuntime.getRuntime().availableProcessors();Get, and then set.
  2. IO intensive IO is very resource-intensive, so we need to calculate how many tasks there are in a large IO program. In general, the maximum number of large tasks in the thread pool > the number of large tasks is generally set to the number of large tasks *2

Here we use an example to better understand the position and role of these parameters in the thread pool. In figure 1.0, we have a bank

    public static  void test05ExecutorService threadPool = new ThreadPoolExecutor(// Number of core threads 2, // maximum number of threads 5, // lifetime of free threads 3, // lifetime in timeunit.seconds, // Here we use the blocking queue that most thread pools use by default, And the capacity of 3 new LinkedBlockingDeque < > (3) and Executors. DefaultThreadFactory (), / / we use the default thread pool all the default use refusal strategies. New ThreadPoolExecutor AbortPolicy ()); Try {// Perform ten print tasks on the threadfor(int i = 1; i <= 2; i++){
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"=> Execution complete!"); }); } } catch (Exception e) { e.printStackTrace(); } finally {// ThreadPool.shutdown (); }}Copy the code

We did print two tasks and found that the thread pool was only using our core two threads, which meant only two people needed to be served, so we opened two counters.

Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required.Copy the code

But when we change the print task to greater than 5, (we change it to 8) we can find that all five threads of the thread pool are in use, so many people that our bank needs to be open for service.

for(int i = 1; i <= 8; i++)
Copy the code
Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required. Pool-1-thread-3 => No further action is required. Pool-1-thread-4 => No further action is required. Pool-1-thread-5 => No further action is required.Copy the code

When we change it to greater than 8, we can see that the rejection policy is triggered. The bank couldn’t hold any more, so we outmaneuvered the man outside.

for(int i = 1; i <= 9; i++)
Copy the code

Thread pool size = Maximum threads + blocking queue size

The blocking queue we are using above is the one used by most thread pools, so this raises the following question.

Why do most thread pools use LinkedBlockingQueue?

  • LinkedBlockingQueue uses a one-way list to declare LinkedBlockingQueue without specifying queue length integer. MAX_VALUE and create a new Node object with item. Next refers to the next Node object in the list. At the beginning, the head and last of the list refer to this Node object. Item and next are null. LinkedBlockingQueue declares a lock for both put and take. This is more efficient.
  • If the array is too large, it will waste memory. If the array is too small, the concurrency performance is poor. If the array is full, it will not be able to place elements in the array unless another thread fetkes the elements. Less efficient than LinkedBlockingQueue.

Four kinds of strategy

When using ThreadPoolExecutor, you can choose your own rejection strategy, and we know of four rejection strategies.

  • AbortPolicy (thrown exception rejected)
  • CallerRunsPolicy(execute using the caller’s thread, go back to where it came from)
  • DiscardOldestPolicy DiscardOldestPolicy DiscardOldestPolicy DiscardOldestPolicy DiscardOldestPolicy
  • DiscardPolicy(silently discard exceptions)

AbortPolicy

The AbortPolicy rejection policy we used above raises an exception when the print task exceeds the thread pool size.

CallerRunsPolicy

We changed the rejection policy to CallerRunsPolicy, and when we executed it, we could see that since the ninth print task was rejected, it was executed by the caller’s thread, which is our main thread. (Because it came from the main thread, and now it’s back to the main thread. So we say it goes back to where it came from.)

ExecutorService threadPool = new ThreadPoolExecutor(// Number of core threads 2, // maximum number of threads 5, // Lifetime of free threads 3, // lifetime in timeunit. SECONDS, // Here we use the blocking queue that most thread pools use by default, And the capacity of 3 new LinkedBlockingDeque < > (3) and Executors. DefaultThreadFactory (), / / we use the default thread pool all the default use refusal strategies. New ThreadPoolExecutor CallerRunsPolicy ());Copy the code
Pool-1-thread-2 => No further action is required. Main => Execution complete! Pool-1-thread-2 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-3 => No further action is required. Pool-1-thread-4 => No further action is required. Pool-1-thread-5 => No further action is required.Copy the code

DiscardOldestPolicy

Tried to compete for the first task and failed. I don’t show it here, I don’t throw an exception.

Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required. Pool-1-thread-3 => No further action is required. Pool-1-thread-4 => No further action is required. Pool-1-thread-5 => No further action is required.Copy the code

DiscardPolicy

The extra tasks, silently abandoned, do not throw exceptions.

Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-1 => No further action is required. Pool-1-thread-2 => No further action is required. Pool-1-thread-3 => No further action is required. Pool-1-thread-4 => No further action is required. Pool-1-thread-5 => No further action is required.Copy the code

DiscardOldestPolicy has the same result as DiscardPolicy, but they are actually different. As we concluded at the beginning, DiscardOldestPolicy will try to compete for additional printing tasks instead of discarding them directly. But obviously a failed competition would not have the same result as DiscardPolicy. But you can see that if you have a lot of threads.