Thread pool
introduce
The time required for a thread to complete a task is:
- Thread creation time - Time1
- Time the task was executed in the thread - Time2
- Thread destruction time - Time3
Why do I need a thread pool
- Thread pool technology focuses on how to shorten or adjust the time of Time1 and Time3, so as to improve the performance of the program. In the project, Time1 and Time3 can be arranged in the start and end time periods of the project or some idle time periods respectively
- Thread pool not only adjusts the time period generated by Time1 and Time3, but also significantly reduces the number of threads created and improves the reuse rate of threads
- The cost of starting a new thread is relatively high because it involves interaction with the operating system. In this case, the use of thread pool can improve the performance, especially when a large number of threads with short lifetime need to be created in the program, the use of thread pool is preferred
Thread pool provided by Java
ExecutorService: interface of thread pool
Executors: tool classes for creating various thread pools
public class Test { public static void main(String[] args) { //Create a thread pool for a single thread //ExecutorService pool = Executors.newSingleThreadExecutor(); //Creates a thread pool for the specified thread //ExecutorService pool = Executors.newFixedThreadPool(3); //Create a thread pool that can cache threads and automatically recycle 60s idle threads ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 1; i <= 100; i++) { pool.execute(new Task(i));//Submit the task, i is the task number (easy to understand) } pool.shutdown();//Close thread pool } } class Task implements Runnable{ private int i; public Task(int i) { this.i = i; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + i); try { Thread.sleep(1000);//Sleep for one second } catch (InterruptedException e) { e.printStackTrace(); } } }
Deep source code
ExecutorService pool = Executors.newSingleThreadExecutor();// Thread pool for a single thread
ExecutorService pool = Executors.newFixedThreadPool(3);// Creates a thread pool for the specified thread
ExecutorService pool = Executors.newCachedThreadPool();// Create a thread pool of cacheable threads. Threads idle for 60 seconds are automatically recycled
The bottom layer of the three thread pools is the object of the ThreadPoolExecutor class
-- analysis ThreadPoolExecutor Class construction method source code-------------------------------- public ThreadPoolExecutor( int corePoolSize, ------------- Number of core threads int maximumPoolSize, ------------- Maximum number of threads long keepAliveTime, ------------- Idle time, which acts on threads between the number of core threads and the maximum number of threads TimeUnit unit, ------------- keepAliveTime Time unit(Can be milliseconds, seconds....) BlockingQueue<Runnable> workQueue, -- Task queue ThreadFactory threadFactory, -------- Thread factory RejectedExecutionHandler handler ---- Processing scheme when thread limit and queue capacity are reached(Reject policy) ) {} Perform steps: 1.After creating thread pool 2.After the task is submitted, check whether there is a core thread: 3.1 No, -> Just create the core thread -> Perform tasks -> After execution, it returns to the thread pool 3.2 have -> To see if there are idle core threads: 4.1 have -> Perform tasks -> After execution, it returns to the thread pool 4.2 No, -> Check whether the current number of core threads is the number of core threads: 5.1 no -> Just create the core thread -> Perform tasks -> After execution, it returns to the thread pool 5.2 yes -> To see if the task list is full: 6.1 No, -> Put it in the list and wait for idle threads to appear 6.2 pack full -> Check whether there are normal threads(The number of threads between the number of core threads and the maximum number of threads) 7.1 No, -> Just create a normal thread -> Perform tasks -> After execution, it returns to the thread pool 7.2 have -> Check whether there are idle ordinary threads 7.1.1 have -> Perform tasks -> After execution, it returns to the thread pool 7.1.2 No, -> Check whether the current number of all threads is the maximum number of threads: 8.1 yes -> Execute the handling scheme (reject policy, default handling, throw exception) 8.2 no ->Just create a normal thread-> Perform tasks -> After execution, it returns to the thread pool be careful: 1.For better understanding, we distinguish between core threads and ordinary threads here. In fact, the distinction is not so clear. They are all threads, which are only easy to understand and use in the bottom layer 2.The default solution is to throw RejectedExecutionException,(Default processing scheme: reject policy (error is reported by default) Summary: the core thread is fully loaded -> Task queue -> Normal thread ->Reject policy (default error) -- Analyze the source code of the thread pool of a single thread -------------------------------- ExecutorService pool = Executors.newSingleThreadExecutor(); new ThreadPoolExecutor( 1, -- Number of core threads 1, -- Maximum number of threads 0L, -- Idle time TimeUnit.MILLISECONDS, -- Time unit(millisecond) new LinkedBlockingQueue<Runnable>() -- Unbounded task queue, you can add tasks indefinitely ) Because there is only one core thread, when the core thread is in use, the task queue has been adding tasks wirelessly, and the unbounded task queue has no range requirements, which will lead to memory overflow and throw an error -- Analyze the source code of the thread pool of the specified thread -------------------------------- ExecutorService pool = Executors.newFixedThreadPool(3); new ThreadPoolExecutor( nThreads, -- Number of core threads nThreads, -- Maximum number of threads 0L, -- Idle time TimeUnit.MILLISECONDS, -- Time unit(millisecond) new LinkedBlockingQueue<Runnable>()-- Unbounded task queue, you can add tasks indefinitely ) Similarly, in the specified thread pool, the maximum number of core threads listed above is 3. When the core thread is in use, the task is added to the task queue wirelessly. Ordinary threads cannot be used and can only be called in malicious threads. The unbounded task queue reaches a certain number, resulting in memory overflow -- Create a thread pool that caches threads ----------------------------------- ExecutorService pool = Executors.newCachedThreadPool(); new ThreadPoolExecutor( 0, -- Number of core threads Integer.MAX_VALUE,-- Maximum number of threads 60L, -- Idle time TimeUnit.SECONDS, -- Time unit(second) new SynchronousQueue<Runnable>() -- Direct submission queue(Synchronization queue): No capacity queue ) The number of cacheable threads has no core threads, and the maximum number of threads is more than 2.14 billion. In real life, no matter what project, so many threads are not used, which will lead to a waste of resources and no order
Detailed explanation of task queue
Queue name | Explain in detail |
---|---|
LinkedBlockingQueue unbounded task queue | Using unbounded task queue, the task queue of the thread pool can add new tasks without limit, and the maximum number of threads created by the thread pool is the number set by your corePoolSize, that is, in this case, the parameter maximumPoolSize is invalid, even if many unexecuted tasks are cached in your task queue, when the number of threads in the thread pool reaches corePoolSize, There will be no more; If a new task is added later, it will directly enter the queue and wait. When using this task queue mode, you must pay attention to the coordination and control between task submission and processing, otherwise the tasks in the queue will grow until the last resources are exhausted due to the inability to process them in time |
Synchronous queue synchronizes the task queue and submits the task queue directly | Using the direct submit task queue, the queue has no capacity. Every insert operation will be blocked, and you need to perform another delete operation to wake up. On the contrary, each delete operation will also wait for the corresponding insert operation. When the task queue is synchronous queue and the number of threads created is greater than maximumPoolSize, the rejection policy is directly executed and an exception is thrown. Using the SynchronousQueue queue, the submitted tasks will not be saved, but will always be submitted for execution immediately. If the number of threads used to execute the task is less than maximumPoolSize, try to create a new thread. If the maximum value set by maximumPoolSize is reached, execute the rejection policy according to the handler you set. Therefore, the tasks submitted by you in this way will not be cached, but will be executed immediately. In this case, you need to have an accurate evaluation of the concurrency of your program before you can set the appropriate number of maximumPoolSize, otherwise it is easy to implement the rejection policy; |
ArrayBlockingQueue bounded task queue | Using bounded task queue, if a new task needs to be executed, the thread pool will create a new thread. Until the number of threads created reaches corePoolSize, the new task will be added to the waiting queue. If the waiting queue is full, that is, it exceeds the capacity initialized by ArrayBlockingQueue, continue to create threads until the number of threads reaches the maximum number of threads set by maximumPoolSize. If it is greater than maximumPoolSize, execute the reject policy. In this case, the upper limit of the number of threads is directly related to the state of the bounded task queue. If the initial capacity of the bounded queue is large or does not reach the overload state, the number of threads will always remain below the corePoolSize. On the contrary, when the task queue is full, the maximum poolsize will be used as the upper limit of the maximum number of threads. |
PriorityBlockingQueue priority task queue | The priority task queue is actually a special unbounded queue. No matter how many tasks are added, the number of threads created by the thread pool will not exceed the number of corePoolSize. However, other queues generally process tasks according to the first in first out rule, while the PriorityBlockingQueue queue queue can customize rules and execute them according to the priority order of tasks. |
Instructions for using priority queues:
public class Test { public static void main(String[] args) { Core thread maximum threads time millisecond ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 200, 1000, TimeUnit.MILLISECONDS //Priority blocking queue Default factory , new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i = 1; i <= 10; i++) { //Execute commit pool.execute(new Task(i)); } //close resource pool.shutdown(); } } //Built in comparator interface class Task implements Runnable,Comparable<Task>{ private int priority;//priority public Task(int priority) { this.priority = priority; } @Override public void run() { try { Thread.sleep(1000);//Rest for 1 second System.out.println(Thread.currentThread().getName() + " priority: " + this.priority); } catch (InterruptedException e) { e.printStackTrace(); } } //When comparing the current object with other objects, it returns - 1 if the current priority is high and 1 if the priority is low. The smaller the value, the higher the priority @Override public int compareTo(Task o) {//External comparator method return (this.priority>o.priority)?-1:1; } } Summary: except that the first task is directly executed by creating a thread, other tasks are put into the priority task queue, rearranged and executed according to priority, and the number of threads in the thread pool is always corePoolSize,That is, there is only one.
Reject policy
ThreadPoolExecutor comes with four rejection policies, all of which implement the RejectedExecutionHandler interface
For example: new ThreadPoolExecutor AbortPolicy()
Reject policy | explain |
---|---|
AbortPolicy | When a task added to the thread pool is rejected, a RejectedExecutionException exception will be thrown, which is the default rejection policy of the thread pool. The exceptions thrown must be handled properly, otherwise the current execution process will be interrupted and subsequent task execution will be affected. |
DiscardPolicy | When a task added to the thread pool is rejected, it is directly discarded, and there is nothing else |
CallerRunsPolicy | When a task added to the thread pool is rejected, the thread pool will add the rejected task to the running thread of the thread pool. Generally, concurrency is relatively small, performance requirements are not high, and failure is not allowed. However, because the caller runs the task himself, if the task submission speed is too fast, it may lead to program blocking and inevitable loss of performance and efficiency |
DiscardOledestPolicy | When a task added to the thread pool is rejected, the thread pool will discard the task at the end of the blocking queue (the oldest task - the first added task), and then add the rejected task to the end. If there is a need to allow the loss of tasks in the project, it can be used |
Custom reject policy
public class Test { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r.toString()+"Reject policy implemented"); } }); for (int i = 1; i <= 10; i++) { pool.execute(new Task()); } pool.shutdown(); } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
Custom thread pool reason
In Alibaba java development manual, it is pointed out that thread resources must be provided through the thread pool, and it is not allowed to create threads displayed in the application. On the one hand, it makes the creation of threads more standardized and can reasonably control the number of threads; On the other hand, the detail management of threads is handed over to the thread pool, which optimizes the cost of resources. Thread pools are not allowed to be created using Executors, but through ThreadPoolExecutor. On the one hand, although the Executor framework in jdk provides methods to create thread pools, such as newFixedThreadPool(), newsinglethreadexecution(), newCachedThreadPool(), they all have their limitations, are not flexible enough, and there is a risk of resource exhaustion (OOM - Out Of Memory).
Generally, when we create a thread pool, in order to prevent resources from being exhausted, the task queue will choose to create a bounded task queue. However, in this mode, if the task queue is full and the number of threads created by the thread pool reaches the maximum number of threads you set, you need to specify the RejectedExecutionHandler parameter of ThreadPoolExecutor, that is, a reasonable rejection policy, To handle the "overload" of the thread pool
Custom thread pool
Prerequisite: learn how to create threads in the thread pool:
Threads in the thread pool are created through ThreadFactory in ThreadPoolExecutor. By customizing the ThreadFactory, you can set some special settings for the threads created in the thread pool, such as naming, priority, etc
public class Test { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 10,20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadFactory() { int threadNum = 1; @Override public Thread newThread(Runnable r) { Thread t = new Thread(r,"thread "+threadNum++); return t; } }, new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r.toString()+"Reject policy implemented"); } }); for (int i = 1; i <= 10; i++) { pool.execute(new Task()); } pool.shutdown(); } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
ThreadPoolExecutor extension
ThreadPoolExecutor extension mainly focuses on the rewriting of beforeExecute(), afterExecute() and terminated(). Through these three methods, we can monitor the start and end time of each task, or some other functions.
method | explain |
---|---|
beforeExecute | Tasks in the thread pool execute before running |
afterExecute | The tasks in the thread pool are executed after running |
terminated | Execute after thread pool exits |
Now we can implement it through code
ThreadPoolExecutor pool = new ThreadPoolExecutor( 10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10), new ThreadFactory() { int threadNum = 1; @Override public Thread newThread(Runnable r) { Thread t = new Thread(r,"thread "+threadNum++); return t; } }, new ThreadPoolExecutor.AbortPolicy() ){ @Override protected void beforeExecute(Thread t,Runnable r) { System.out.println("Ready to execute:"+ Thread.currentThread().getName()); } @Override protected void afterExecute(Runnable r,Throwable t) { System.out.println("Execution completed:"+ Thread.currentThread().getName()); } @Override protected void terminated() { System.out.println("Thread pool exit"); } }; for (int i = 1; i <= 10; i++) { pool.execute(new Task()); } pool.shutdown(); } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println("In progress:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
Number of threads in thread pool
sysbench multithreading performance testing tool is used in practical work
summary
Executor: the top-level interface of the thread pool
ExecutorService thread pool interface, submit task code through submit
Executors factory class: a thread pool can be obtained through the class
Get the specified number of thread pools through newFixedThredPool (int nThreads). Parameters: specify the number of threads in the thread pool
Get the polymorphic number of thread pools through newCachedThreadPool(). If it is not enough, create a new one without an upper limit
Callable thread task, Futrue asynchronous return value
Lock, ReentrantLock, reentrant readwritelock
CopyOnWriteArrayList thread safe ArrayList
CopyOnWriteArraySet thread safe Set
ConcurrentHashMap thread safe HashMap
ConcurrentLinkedQueue thread safe Queue
ArrayBlockingQueue thread safe blocking Queue. (producer, consumer)