Threads are often involved in work. For example, some tasks are often handed over to threads for asynchronous execution. Or the server program establishes a separate thread processing task for each request. Beyond threads, such as the database connection we use. These creation, destruction or opening and closing operations have a great impact on system performance. Therefore, the use of "pool" is highlighted.
1. Why use thread pool
In the implementation described in Section 3.6.1, a new worker thread is assigned to each client. When the worker thread communicates with the client, the thread is destroyed. This implementation method has the following shortcomings:
•The overhead of server creation and destruction (including time and system resources) is very high. This item does not need to be explained, you can check the "thread creation process". In addition to the work done by the machine itself, we also need to instantiate and start, which all require occupancy of stack resources.
•In addition to the overhead of creating and destroying threads, active threads also consume system resources. This should be a consumption of stack resources. It is also considered to guess the number of database connections.
•If the number of threads is fixed and each thread has a long declaration cycle, then thread switching is also relatively fixed. Different operating systems have different switching cycles, usually around 20ms. The switch mentioned here is to transfer the CPU usage rights between threads under the scheduling of JVM and the underlying operating system. If threads are frequently created and destroyed, threads will be switched frequently, because after a thread is destroyed, the right to use must be given to the ready thread, so that the thread can get a chance to run. In this case, switching between threads does not follow the system's fixed switching cycle, and the overhead of switching threads is even greater than the overhead of creation and destruction.
Relatively speaking, using thread pools, some threads are pre-created, which constantly remove tasks from the work queue and then execute the task. When the worker thread completes one task, it continues to execute another task in the work queue. The advantages are as follows:
• Reduces the number of creation and destruction, and each worker thread can be reused all the time and can perform multiple tasks.
• The number of threads in the thread pool can be easily adjusted according to the system's carrying capacity to prevent system crashes due to excessive system resources.
2. Simple implementation of thread pool
Below is a simple thread pool written by myself, which was also directly drawn from the Java network programming book
package thread;import java.util.LinkedList;/** * Implementation of thread pool, based on the length, maximum length, and queue length of the regular thread pool, we can increase the number of implementations* @author Han */public class MyThreadPool extends ThreadGroup{ //cpu number---Runtime.getRuntime().availableProcessors(); // Whether to close private boolean isClosed = false; //queue private LinkedList<Runnable> workQueue; //thread pool id private static int threadPoolID; private int threadID; public MyThreadPool(int poolSize){ super("MyThreadPool."+threadPoolID); threadPoolID++; setDaemon(true); workQueue = new LinkedList<Runnable>(); for(int i = 0;i<poolSize;i++){ new WorkThread().start(); } } // Here you can change to ConcurrentLinkedQueue to avoid the efficiency of using synchronized public synchronized void execute(Runnable task){ if(isClosed){ throw new IllegalStateException("Connection pool has been closed..."); }else{ workQueue.add(task); notify(); } } protected synchronized Runnable getTask() throws InterruptedException { while(workQueue.size() == 0){ if(isClosed){ return null; } wait(); } return workQueue.removeFirst(); } public synchronized void close(){ if(!isClosed){ isClosed = true; workQueue.clear(); interrupt(); } } public void join(){ synchronized (this) { isClosed = true; notifyAll(); } Thread[] threads = new Thread[activeCount()]; int count = enumerate(threads); for(int i = 0;i<count;i++){ try { threads[i].join(); } catch (Exception e) { } } } class WorkThread extends Thread{ public WorkThread(){ super(MyThreadPool.this,"workThread"+(threadID++)); System.out.println("create..."); } @Override public void run() { while(!isInterrupted()){ System.out.println("run.."); Runnable task = null; try { //This is a blocking method task = getTask(); } catch (Exception e) { } if(task != null){ task.run(); }else{ break; } } } } }}This thread pool mainly defines a work queue and some pre-created threads. As long as the execute method is called, you can submit tasks to the thread.
When there is no task, the subsequent thread will block in getTask() until a new task comes in and is awakened.
Both join and close can be used to close the thread pool. The difference is that join will complete the tasks in the queue, while close will immediately clear the queue and interrupt all worker threads. The interrupt() in close() is equivalent to calling the respective interrupt() containing child threads in ThreadGroup. Therefore, when a thread is waiting or sleep, an InterruptException will be thrown.
The test class is as follows:
public class TestMyThreadPool { public static void main(String[] args) throws InterruptedException { MyThreadPool pool = new MyThreadPool(3); for(int i = 0;i<10;i++){ pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("working..."); } }); } pool.join(); //pool.close(); }}3. The thread pool provided by the jdk class library
Java provides a good thread pool implementation, which is more robust and efficient than our own implementation, and also has more powerful functions.
The class diagram is as follows:
The seniors have already given a good explanation about this type of thread pool. Any Java thread pool under Baidu has very detailed examples and tutorials written, so I will not repeat them here.
4. Spring injection thread pool
When using the spring framework, if we use the methods provided by Java to create a thread pool, it is very inconvenient to manage in multi-threaded applications and does not conform to our idea of using spring. (Although spring can be injected by static methods)
In fact, Spring itself also provides a good implementation of thread pools. This class is called ThreadPoolTaskExecutor.
The configuration in spring is as follows:
<bean id="executorService"> <property name="corePoolSize" value="${threadpool.corePoolSize}" /> <!-- Minimum number of threads maintained by thread pool --> <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" /> <!-- Allowed free time for thread pool maintenance threads--> <property name="maxPoolSize" value="${threadpool.maxPoolSize}" /> <!-- Maximum number of threads maintained by thread pool --> <property name="queueCapacity" value="${threadpool.queueCapacity}" /> <!-- The buffer queue used by thread pool--> </bean>5. Notes on using thread pools
•Deadlock
Any multithreaded program has the risk of deadlocking. The simplest case is that two threads AB, A holds lock 1, request lock 2, B holds lock 2, and request lock 1. (This situation will also appear in mysql exclusive lock, and the database will directly report an error message). There is another deadlock in the thread pool: assuming that all worker threads in the thread pool are blocked while executing their respective tasks, and they are waiting for the execution result of a certain task A. Task A is in the queue and cannot be executed because there are no idle threads. In this way, all resources of the thread pool will be blocked and deadlocks will be generated.
•Insufficient system resources
If the number of threads in the thread pool is very large, these threads will consume a large amount of resources, including memory and other system resources, which seriously affects system performance.
•Concurrent error
The work queue of the thread pool relies on the wait() and notify() methods to enable the worker to obtain tasks in a timely manner, but these two methods are difficult to use. If the code is wrong, notifications may be lost, causing the worker thread to remain idle, ignoring the tasks that need to be processed in the work queue. Because it is best to use some more mature thread pools.
•Thread Leakage
A serious risk of using thread pools is thread leakage. For thread pools with fixed number of worker threads, if the worker thread throws a RuntimeException or Error when executing a task, and these exceptions or errors are not caught, the worker thread terminates abnormally, causing the thread pool to permanently lose a thread. (This is so interesting)
Another situation is that the worker thread is blocked when executing a task. If it is waiting for the user's input data, but the user does not input data, resulting in the thread being blocked all the time. Such a worker thread is in name only, and it does not actually perform any tasks. If all threads in the thread pool are in this state, the thread pool cannot add new tasks.
•Task overload
When there are a large number of tasks queued to be executed in the worker thread queue, these tasks themselves may consume too much system resources and cause resource shortages.
To sum up, when using thread pools, the following principles should be followed:
1. If task A needs to wait for the execution result of task B synchronously during execution, then task A is not suitable to be added to the work queue of the thread pool. If you need to wait for other tasks to execute results like Task A to be added to the queue, it may cause a deadlock
2. If a task may be blocked and is blocked for a long time, the timeout time should be set to avoid permanent blocking of the worker thread and causing thread leakage. In the server program, when a thread is waiting for the client to connect or wait for the data sent by the client, it may cause blockage. You can set the time in the following ways:
Call the setSotimeout method of ServerSocket to set the timeout time to wait for the client to connect.
For each socket connected to the customer, call the setSoTImeout method of the socket to set the timeout time waiting for the customer to send data.
3. Understand the characteristics of the task and analyze whether the task performs operations that often block io operations, or performs operations that will not block. The former occupies CPU intermittently, while the latter has a higher utilization rate. How long does it take to complete a task? Is it a short-term task or a long-term task? Then, classify the tasks according to the characteristics of the task, and then add different types of tasks to the work queue of different thread pools. In this way, each thread pool can be allocated and adjusted according to the characteristics of the task.
4. Resize the thread pool. The optimal size of a thread pool mainly depends on the number of available CPUs in the system and the characteristics of tasks in the work queue. If there is only one work queue on a system with N CPUs, and all of them are tasks with arithmetic nature (not blocking), then when the thread pool has N or N+1 worker threads, the maximum CPU usage will generally be obtained.
If the work queue contains tasks that perform IO operations and often block, make the thread pool size exceed the number of available CPUs, because not all work threads are working all the time. Select a typical task and then estimate the ratio of the waiting time to the time that actually occupies CPU to perform operations in the project that performs this task. For a system with N CPUs, approximately N*(1+WT/ST) threads need to be set to ensure that the CPU is fully utilized.
Of course, CPU utilization is not the only thing to consider in the process of adjusting thread pools. As the number of thread pool work increases, memory or other resource restrictions will also occur, such as sockets, open file handles or database connections, etc. It is necessary to ensure that the system resources consumed by multithreads are within the scope of the system's endurance.
5. Avoid task overload. The server should limit the number of customers' concurrent connections based on the system's carrying capacity. When the client's connection exceeds the limit value, the server can reject the connection and make friendly prompts, or limit the queue length.
The above several implementation methods and answers to Java thread pools are all the content I have shared with you. I hope you can give you a reference and I hope you can support Wulin.com more.