On thread pool in high concurrency and multithreading
definition
Thread is a scarce resource. Its creation and destruction is a relatively heavy and resource consuming operation. Java thread depends on kernel thread. Creating thread requires operating system state switching. In order to avoid excessive resource consumption, it is necessary to try to reuse thread to perform multiple tasks. Thread pool is a thread cache, which is responsible for unified allocation, tuning and monitoring of threads.
When to use thread pools:
- The processing time of a single task is relatively short
- The number of tasks to be processed is relatively large
Advantages of thread pool:
- Reuse existing threads, reduce the overhead of thread creation and extinction, and improve performance
- Improve response speed. When the task arrives, the task can be executed immediately without waiting for the thread to be created.
- Improve thread manageability, unified allocation, tuning and monitoring.
How thread pools are created
Executors under JavaJUC package provide four ways to create thread pools and underlying parameters (the underlying of the four ways to create threads uses the ThreadPoolExecutor class, which is defined according to different parameters, and the description of relevant parameters will be given below)
newCachedThreadPool
Definition and function
Create a thread pool that can create new threads as needed. If there are available threads in the thread pool (available means that the threads exist and are idle), if not, create a new thread to execute. It is usually used to execute asynchronous tasks with short time
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//The number of core threads corePoolSize is 0, and maximumPoolSize is close to infinity 60L, TimeUnit.SECONDS,//The survival time of keepAliveTime is 60s, and it will be recycled automatically when the time is up new SynchronousQueue<Runnable>());//Synchronous blocking queue is used }
Usage example
//Define a thread class class RunnableThread implements Runnable{ private int i=0; public RunnableThread(int i) { this.i = i; } public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"The second paragraph was implemented"+i+"A task!"); } } //Create 10 tasks ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.execute(new RunnableThread(i)); }
You can see that 10 tasks were performed in one second
newScheduledThreadPool
Definition and function
Create a thread pool that supports periodic execution and can be used as a scheduled task. It is mainly used in the scenario of periodic task execution
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,//The maximumPoolSize is close to infinity, the keepAliveTime lifetime is 0 nanoseconds, and the thread will not be recycled new DelayedWorkQueue());//The delay queue is adopted, and the submitted tasks are sorted into the queue according to the execution time }
Usage example
//schedule is a method unique to ScheduledExecutorService ScheduledExecutorService executorService = Executors.newScheduledThreadPool(6); executorService.schedule(new RunnableThread(1),6L,TimeUnit.SECONDS); executorService.schedule(new RunnableThread(2),5L,TimeUnit.SECONDS); executorService.schedule(new RunnableThread(3),4L,TimeUnit.SECONDS); executorService.schedule(new RunnableThread(4),3L,TimeUnit.SECONDS); executorService.schedule(new RunnableThread(5),2L,TimeUnit.SECONDS);
You can see that tasks are executed according to the preset delay. If timed tasks need to be done, you can use this thread to implement them
newFixedThreadPool
Definition and function
Create a thread pool with a fixed number of threads as needed. When there are many tasks, queue up and wait. It is suitable for the scenario with a fixed number of tasks and stability, that is, to create the specified number of threads when the concurrency pressure is determined
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,//The number of core threads is equal to the maximum number of threads 0L, TimeUnit.MILLISECONDS,//nanosecond new LinkedBlockingQueue<Runnable>()); //The queue is blocked. When there are no available threads when the task volume comes, the queue will wait }
Usage example
ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 6; i++) { executorService.execute(new RunnableThread(i)); }
You can see that three threads execute once in the first second, and the three threads execute repeatedly in the second second second, without creating a new thread
newSingleThreadExector
Definition and function
Create a thread pool with only a single thread and use the only working thread to execute tasks to ensure the orderly execution of tasks. It is applicable to the case where tasks are required to be carried out in order. It is the same as the definition of newFixedThreadPool, but there is only one thread
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
Usage example
ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.execute(new RunnableThread(i)); }
You can see that the tasks are executed one by one in a second and are carried out in an orderly manner
The above is the creation of four thread pools provided by Executors. It is clear from the above that the bottom layer is determined by using ThreadPoolExecutor according to different parameters. This class is mainly used to analyze the underlying principle of thread pool.
ThreadPoolExecutor
//Construction method public ThreadPoolExecutor(int corePoolSize, //Number of core threads int maximumPoolSize, //Maximum number of threads long keepAliveTime, //survival time TimeUnit unit, //Time unit BlockingQueue<Runnable> workQueue, //Using the queue type, tasks can be stored in the task queue and wait to be executed. The FIFIO principle (first in, first out) is executed ThreadFactory threadFactory, //It is a thread worker that creates threads. You can create a thread in your own custom way RejectedExecutionHandler handler)//It is a rejection policy. After a task is full, we can refuse to execute some tasks according to the adopted policy. java provides four execution strategies: // AbortPolicy: interrupt throws an exception //Discard policy: silently discard the task without any notification //DiscardOldestPolicy: discards the task that has been in the queue for the longest time //CallerRunsPolicy: let the thread submitting the task execute the task (compared with the first three, it's more friendly)
Thread execution principle (it will be more clear in combination with the source code)
In actual project development, it is also recommended to use the method of manually creating thread pool instead of the default method. This is described in Alibaba development specification:
Create a custom thread pool, and create a thread pool according to the concurrency and specific task requirements
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(10)); for (int i = 0; i < 100; i++) { threadPoolExecutor.execute(new RunnableThread(i) ); }
execute source code: the description of the source code will be supplemented later
int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { //If the number is less than the number of core threads, execute down if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { // int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); //Adopt rejection strategy private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
That's all for the basic use and principle of thread pool. You are welcome to point out your shortcomings and learn together!!