Background to the accident

A brief description of the business scenario is given. There is a distributed timed job in the project, and data is retrieved periodically. Then there are two layers of cycles. That’s a simple business background. This has just entered the line of small white code to pay attention to!

As a programmer is also the first time I met this problem, although this problem is very simple, but the impact is very big, I think it’s necessary to make a small summary, after friends write similar code will have notice that when a colleague is the cause of the problem I didn’t fully understand the thread pool, leading to a big problem. So let’s get to the thread pool before we get to the problem.

The thread pool

Let me start by saying that custom thread pools are common in the enterprise, and few of the thread pool methods provided by the JDK are used in order to achieve a controllable thread pool.

Here to help you just a simple review of the specific thread pool online has been a large number of articles.

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) 
   
private static ThreadPoolExecutor poolExecutor = new
            ThreadPoolExecutor(3,
            30,
            1,
            TimeUnit.MINUTES,
            new ArrayBlockingQueue(1000),
            new MyRejectedExecutionHandler(MAX_QUEUE_SIZE));
Copy the code

So there are only a few parameters to understand when talking about thread pools.

process

  • If we define corePoolSize to be 5 maximunPoolSize to be 30 and keepAliveTime to be 1 minute, the queue is about 1000
  • The first Task comes in and is put into the queue. The thread pool keeps fetching from the queue. When it gets to the first Task, it finds that the number of threads in the current thread pool is less than corePoolSize.
  • The second Task is fetched from the queue and the thread pool finds that the current thread is still smaller than the corePoolSize
  • Now the fifth Task is equal to corePoolSize, ok, continue to create a thread. The five created threads are not destroyed, which is why the thread pool avoids the overhead of creating thread generations repeatedly.
  • The sixth Task comes in, and the thread pool finds the thread that just finished executing in corePoolSize, so it doesn’t need to create a new thread and uses it directly, achieving a reuse effect.
  • If our Task is too many and time-consuming, and corePoolSize is running out of free threads, then maximumPoolSize comes in. CorePoolSize < num < maximumPoolSize if corePoolSize < num < maximumPoolSize if corePoolSize < num < maximumPoolSize
  • If num > maximumPoolSize, no new thread is created and new tasks are added to the queue
  • When the queue inside have exceeded the maximum number (1000) we define here, rather we have full thread pool can’t receive the new Task, so sorry brother, I can reject you, and then go our MyRejectedExecutionHandler
  • CorePoolSize < num < maximumPoolSize exceeds the lifetime of the corePoolSize idle thread. For example, if the core is 5 and the maximum is 30, then the extra 25 threads are the excess threads, which can be summarized simply as: the lifetime of the thread beyond the core thread.

So the core flow of the entire thread pool is described above.

Custom thread pools

The thread pool process and principle have been described above, the following custom thread pool directly post code, do not do too much elaboration.

Public class MyWorkerThreadPool {private static final int MAX_QUEUE_SIZE = 1000; private static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 30, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(MAX_QUEUE_SIZE), new MyRejectedExecutionHandler(MAX_QUEUE_SIZE)); public static void submitTak(Runnable run) { poolExecutor.submit(run); } public static voidshutdown() { poolExecutor.shutdown(); }} / / define a rejection policies public class MyRejectedExecutionHandler implements RejectedExecutionHandler {private final Log logger = LogFactory.getLog(this.getClass()); private int maxQueueSize; public MyRejectedExecutionHandler(int maxQueueSize){ this.maxQueueSize=maxQueueSize; } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Failed to submit the task" +
                "Maximum current queue length" +
                "maxQueueSize="+maxQueueSize+
                ",maximumPoolSize="+executor.getMaximumPoolSize());
        if(executor.getQueue().size()<maxQueueSize){
            executor.submit(r);
        }else{ try { Thread.sleep(3000); executor.submit(r); }catch (Exception e){// This Exception ignores executor.submit(r); }}}}Copy the code

Accident code (pseudocode)

So here is the key point, here is only a part of the pseudo-code, as I said before this is a distributed timing job, here is my colleagues streamlined code extracted.

// Simulate a scenariofor(int i= 0; i< 1000; i++){for(int j = 0; j<20; J++) {MyWorkerThreadPool. SubmitTak (() - > {/ / real business scenario here is very time consuming, try {thread.sleep (5000); } catch (InterruptedException e) { e.printStackTrace(); }}); }} Execution result: MaxQueueSize =1000,maximumPoolSize=30 Maximum queue length maxQueueSize=1000,maximumPoolSize=30 If the task fails to be submitted MaxQueueSize =1000,maximumPoolSize=30 Maximum queue length maxQueueSize=1000,maximumPoolSize=30 If the task fails to be submitted The maximum queue length maxQueueSize=1000,maximumPoolSize=30Copy the code

Here the first layer simulates 1000 data, the second layer loops 30 data, causing our custom thread queue to fill up at the same time, 2000*30 this is how many threads open. If you think about it in detail, what will happen if you use it this way? At present, a lot of the business is rejected and not executed. In addition, when the thread pool is full, other functions of our project will also be rejected to execute asynchronous methods, with predictable results.

So how to solve this, it’s actually very simple, we can run this time-consuming task we can assign 10 threads to run, so that the other functional business will not be affected. I’m using a simple counter here, such as blocking for more than 10 threads and waiting for a while, and then subtracting one after running a thread, and going directly to the code

Public class TestMain {public static void main(String[] args) throws Exception{// Simulate a scenario, this is a distributed scheduled task, AtomicInteger threadNum = new AtomicInteger(0); // Simulate the current first layer has 1000 datafor(int i= 0; i< 1000; I++){// simulate 20 strips of data per linefor(int j = 0; j<20; J++){// multithreaded pull to crawl the network data summary database // a maximum of 10 threads at a time to perform time-consuming operations,whileThread.get () > 10){thread.sleep (500); thread.sleep (500); } / / small increase 1 threadNum. IncrementAndGet (); int tempI = i; int tempJ = j; MyWorkerThreadPool. SubmitTak (() - > {/ / real business scenario here is very time consuming, try {thread.sleep (5000); System.out.println(Thread.currentThread().getName()+"Execute layer 1 data"+ tempI +"Second layer :"+tempJ); / / after reducing a 1 threadNum. DecrementAndGet (); } catch (InterruptedException e) { e.printStackTrace(); }}); }}} Result: Pool-2-thread-2 Data execution 0 Layer 2 :1 Pool-2-thread-1 Data execution 0 Layer 2 :0 Pool-2-thread-3 Data execution 0 Layer 2 :2 Pool-2-thread-2 Data execution 0 Layer 2 :3 Pool-2-thread-3 Completes data execution at layer 1 0 Layer 2 :5 Pool-2-thread-1 completes data execution at layer 1 0 Layer 2 :4 Pool-2-thread-3 completes data execution at layer 1 0 Layer 2 :7 Pool-2-thread-1 completes data execution at layer 2 :8 Pool-2-thread-2 Completes the execution of layer 1 data. 0 Layer 2 :6 Pool-2-thread-1 completes the execution of layer 1 data. 0 Layer 2 :10 Pool-2-thread-3 completes the execution of layer 1 data Pool-2-thread-2 Completes data execution at layer 1. 0 Layer 2 :11 Pool-2-thread-3 completes data execution at layer 1. 0 Layer 2 :13 Pool-2-thread-2 completes data execution at layer 1 Pool-2-thread-1 Completes the execution of layer 1 data 0 and Layer 2 data 12Copy the code

conclusion

There are a lot of small details in development, but sometimes we don’t pay attention to them or understand how they work, and sometimes we end up writing code with bad results. So that’s it for today!