Thread pool rejection strategy and code demonstration

Thread Pool:

In order to avoid creating threads repeatedly, the appearance of thread pools allows threads to be reused. When a task is submitted, a thread is taken from the thread pool. When the task is completed, the thread is not directly closed, but the thread is returned to the thread pool for use by other tasks. In this way, unnecessary performance overhead caused by frequent thread creation can be avoided.

Use thread pool:

After the java1.5 version, the use of thread pools is recommended to use the three static methods that have been packaged under the java.util.concurrent package:

  1. Create a thread pool with a specified thread size
	Executor executor = Executors.newFixedThreadPool (int nThreads);
  1. Create a thread pool with 1 thread
 	Executor executor = Executors.newSingleThreadExecutor();
  1. Do not specify the size, create a thread pool that can dynamically add threads
 	Executor executor = Executors.newCachedThreadPool();

The principle of the thread pool:

In fact, the above three methods are actually the construction methods of the ThreadPoolExecutor class called, but the parameters are inconsistent.Let's take a look at the construction methods of the ThreadPoolExecutor class:

   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;
    }

Use a table to explain the meaning of the parameters required by this method:

parameter name Explanation
int corePoolSize Core pool size
int maximumPoolSize The size of the largest thread pool
long keepAliveTime The maximum survival time of idle threads in the thread pool that exceeds corePoolSize
TimeUnit unit The time unit of keepAliveTime is an enumerated variable
BlockingQueue<Runnable> Blocking task queue
ThreadFactory threadFactory Thread factory
RejectedExecutionHandler handler Thread pool rejection strategy

Thread pool workflow

  1. If the number of threads in the current thread pool is less than corePoolSize, then every time a task is submitted, a thread will be created to execute the task;
  2. If the number of thread pools is greater than corePoolSize, each time a thread is submitted, it will try to submit it to the task cache queue.If the addition is successful, the task will execute the task in the cache queue when the thread is idle; if the addition fails (generally Create a bounded cache queue), then another thread will be created to perform this task. (In this case, the sum of the maximum number of threads MaximumPoolSize and the length of the cache queue is less than the total number of threads you need to create).
  3. If the sum of the maximum number of threads MaximumPoolSize and the length of the cache queue is greater than the total number of threads you need to create, then a task rejection strategy will be adopted for processing;
  4. If the number of threads in the thread pool is greater than corePoolSize, if the idle time of a thread exceeds keepAliveTime, the thread will be terminated until the number of threads in the thread pool is not greater than corePoolSize; if it is allowed to set the survival time for the threads in the core pool, then the core pool The idle time of the thread in more than keepAliveTime, the thread will also be terminated;
Code to demonstrate this workflow
package RejectThread;

//需要执行的任务线程
public class SendNoticeTask implements Runnable {
    private int count;

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println (Thread.currentThread ().getName () + "号工人    开始进行"  + count + "号工作任务");
        try {
            Thread.currentThread ().sleep (5000);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        System.out.println (Thread.currentThread ().getName ()+"号工人   执行完成");
    }
}
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形一
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> ());
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 3; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

Here we set the core pool size of the thread pool to 3, the maximum number of threads is 10, and the queue of the task cache is not set to a size.When the number of threads you submit is less than or equal to 3, the result is basically the same:
Insert picture description hereif we reset the submission The number of tasks, we set the number of threads to be executed to 6, the cache queue does not set the number:

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> ());
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 6; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}


Insert picture description here
Take a look at the results: It can be seen that the first three tasks 0, 1 and 2 are directly executed, and the three tasks 3, 4, 5 are put into the cache queue, and wait until the first three threads are free It is only taken out of the cache queue for execution. At this time, no matter how many threads you submit, the rejection strategy is not implemented, because it can be submitted to the cache queue.
Let's look at the case of adding failure, we set the LinkedBlockingQueue queue size to 1 , That is, only one thread can be put in, and look at the execution result:

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));//队列数量为1
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 6; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}


Insert picture description hereLook at the results of the execution: It can be seen that work tasks 0, 1, 2, 4, and 5 all directly create threads and execute them, while work task 3 is stored in the cache queue and waits for the thread to become available. It is executed.
In fact, this process is that first of all, the three tasks 0, 1, 2 are directly created by the thread to execute, because the size of corePoolSize is 3, and then there are tasks 3, 4, 5, and there is no way to go to the cache. It is added to the queue, but the size of the cache queue is 1, so only one can be added, that is, task 3, tasks 4 and 5 directly create a new thread for execution, and wait for the thread to be idle when it is idle. Take task 3 out of the queue to continue execution.

Case three is explained in the rejection strategy.

Thread pool rejection strategy

This article mainly demonstrates the rejection strategy of the thread pool, there are 4 kinds in total:

Rejection strategy handler Explanation
AbortPolicy Discard the task and throw RejectedExecutionException
DiscardPolicy Discard the task, but do not throw an exception
DiscardOldestPolicy Discard the first task in the queue, and then try to execute the task again (repeat this process)
CallerRunsPolicy The task is handled by the calling thread (the most common is the main thread)

In the above business, when the rejection strategy is used, we set the corePoolSize to 3, the maximumPoolSize is 10, and the cache queue size is 1. This time we perform 15 tasks, this time we will use the rejection strategy, the code is as follows :

  • AbortPolicy: Throw exception rejection policy:
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

The results are as follows: It
Insert picture description herecan be seen that tasks 0, 1, 2 are directly executed first, task 3 enters the cache queue, and new threads are created for execution on 4, 5, 6, 7, 8, 9, 10, and the maximum has been reached at this time. The thread creation number is 10, it cannot be created, and the rejection strategy is executed.The subsequent tasks 11, 12, 13, 14 are directly discarded and throw an exception.

  • Discard tasks without throwing abnormal policies DiscardPolicy
 public DiscardOldestPolicy() { }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardPolicy ());//不抛异常的拒绝策略
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

The execution result is relatively simple, without analysis:
Insert picture description here

  • The calling thread handles the task (the most common is the main thread). The strategy CallerRunsPolicy
    changed the output of the thread that executed the task to facilitate the viewing of the effect:
package RejectThread;

public class SendNoticeTask implements Runnable {
    private int count;

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println (Thread.currentThread ().getName () + "号工人    开始进行"  + count + "号工作任务");
        try {
            Thread.currentThread ().sleep (5000);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        System.out.println (Thread.currentThread ().getName ()+"号工人    执行完成"  + count + "号工作任务~~~~~~");
    }
}

Use CallerRunsPolicy's strategy:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

Note here: here directly call the run () that needs to execute the thread, which is equivalent to directly calling the method (you can understand the difference between calling run () and start () to start the thread), because it is in the main Inside the thread, so it represents the strategy that the executor runs.CallerRun means this in English.

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.CallerRunsPolicy ());
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

View the execution result: The
Insert picture description hereexecution result is special.First, it does not throw an exception.Secondly, the main thread (main) is added.The figure is the first pop-up result.The analysis results can be understood as follows: This strategy is equivalent to the main thread. The thread pool, that is, the maximum thread capacity is now 11, task 3 joined the cache queue we defined, that is, when task 11 was submitted, the rejection strategy was implemented, and the task was assigned to the main thread. The main thread also takes time to execute the task.All the loops for submitting tasks are blocked here. Wait until the main thread finishes execution before continuing to submit the next task.At this time, the threads in the thread pool have already partially completed execution, so they can continue to execute. Later tasks.

  • Discard the task at the head of the queue, and then try to execute the task again (repeat this process) DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        System.out.println("主线程结开始");
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (2));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardOldestPolicy ());
        for (int i = 0; i < 13; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
        System.out.println("主线程结束");

The running results are as follows: The
Insert picture description hereanalysis results can be seen, the previous process is consistent, tasks 0, 1, 2 are created directly, 3, 4 are added to the cache queue, 5 ~ 11 newly created threads are executed, and then you submit 12 Tasks, the system's processing flow is that you submit a new thread, first delete a thread in the cache queue, call the
poolExecutor.execute () method to execute again, until all tasks are up to date, so the cache queue is discarded The first task is task 3, so that tasks 4 and 12 are subsequently executed.

to sum up

The rejection strategy of the thread pool is the above four.If the above still cannot meet your requirements, you need to customize the rejection strategy.The practice is to write a class to implement the RejectedExecutionHandler interface and customize your code logic:

package RejectThread;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class MyRejectPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (!executor.isShutdown()) {
            executor.getQueue().poll ();
            System.out.println("自定义的拒绝策略");
            executor.submit (r);
             //r.run ();
        }
    }
}

Conduct code testing:

package RejectThread;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        System.out.println("主线程结开始");
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<> (2));
        poolExecutor.setRejectedExecutionHandler (new MyRejectPolicy());
        for (int i = 0; i < 14; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            executor.submit (r);  
        }
        System.out.println("主线程结束");

Output results: The
Insert picture description here
previous analysis should be very simple, mainly when submitting task No. 12, it first prints our output, and then uses the thread pool to execute the task.At this time, it needs to wait.

Published 39 original articles · won praise 1 · views 4620

Guess you like

Origin blog.csdn.net/thetimelyrain/article/details/97523175