Home  >  Article  >  Java  >  Several implementation methods of Java thread pool and answers to common questions

Several implementation methods of Java thread pool and answers to common questions

黄舟
黄舟Original
2017-01-20 11:15:171391browse

The following editor will bring you several implementation methods and answers to frequently asked questions about Java thread pools. The editor thinks it’s pretty good, so I’d like to share it with you now and give it as a reference. Let’s follow the editor and take a look.

In work, threads are often involved. For example, some tasks are often handed over to threads for asynchronous execution. Or the server program creates a separate thread processing task for each request. Outside of threads, such as the database connection we use. These creation, destruction, or opening and closing operations greatly affect system performance. Therefore, the usefulness of "pool" comes to the fore.

1. Why use a thread pool

In the implementation introduced in Section 3.6.1, a new worker thread is allocated to each customer. When the communication between the worker thread and the client ends, this thread is destroyed. This implementation has the following shortcomings:

•The overhead of server creation and destruction work (including the time and system resources spent) is very large. There is no need to explain this item. 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 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. I guess setting a reasonable value for the number of database connections is also a consideration.

•If the number of threads is fixed and each thread has a long life cycle, then thread switching is also relatively fixed. Different operating systems have different switching cycles, usually about 20ms. The switching mentioned here is the transfer of CPU usage rights between threads under the scheduling of the 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 usage rights must be given up to the thread that is already ready, so that the thread can get a chance to run. In this case, switching between threads no longer follows the fixed switching cycle of the system, and the cost of switching threads is even greater than the cost of creation and destruction.

Relatively speaking, when using a thread pool, some threads will be pre-created, which will continuously take out tasks from the work queue and then execute the task. When the worker thread finishes executing a task, it will continue to execute another task in the work queue. The advantages are as follows:

• Reduces the number of creation and destruction. Each worker thread can be reused all the time and can perform multiple tasks.

•You can easily adjust the number of threads in the thread pool according to the system's carrying capacity to prevent system crashes due to excessive consumption of system resources.

2. Simple implementation of thread pool

The following is a simple thread pool written by myself, which is also copied directly from the book Java Network Programming

package thread;
 
import java.util.LinkedList;
 
/**
 * 线程池的实现,根据常规线程池的长度,最大长度,队列长度,我们可以增加数目限制实现
 * @author Han
 */
public class MyThreadPool extends ThreadGroup{
  //cpu 数量 ---Runtime.getRuntime().availableProcessors();
  //是否关闭
  private boolean isClosed = false;
  //队列
  private LinkedList<Runnable> workQueue;
  //线程池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();
    }
  }
  //这里可以换成ConcurrentLinkedQueue,就可以避免使用synchronized的效率问题
  public synchronized void execute(Runnable task){
    if(isClosed){
      throw new IllegalStateException("连接池已经关闭...");
    }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 {
          //这是一个阻塞方法
          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, the task can be submitted to the thread.

When the subsequent thread has no tasks, it 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 finish executing 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() of the child threads in the ThreadGroup, so when a thread is in wait 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 is also more powerful.

The class diagram is as follows:

Seniors have already given good explanations about this type of thread pool. Any Java thread pool under Baidu has very detailed examples and tutorials written, so I won’t go into details here.

4. Spring injection thread pool

When using the spring framework, if we use the method provided by java to create a thread pool, it will be very inconvenient to manage in multi-threaded applications, and it is not compliant We use spring ideas. (Although spring can be injected through static methods)

In fact, Spring itself also provides a good thread pool implementation. This class is called ThreadPoolTaskExecutor.

The configuration in spring is as follows:

<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="${threadpool.corePoolSize}" />
    <!-- 线程池维护线程的最少数量 -->
    <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" />
    <!-- 线程池维护线程所允许的空闲时间 -->
    <property name="maxPoolSize" value="${threadpool.maxPoolSize}" />
    <!-- 线程池维护线程的最大数量 -->
    <property name="queueCapacity" value="${threadpool.queueCapacity}" />
    <!-- 线程池所使用的缓冲队列 -->
  </bean>

5. Precautions for using thread pool

•Deadlock

Any multi-threaded program will die The simplest case of lock risk is two threads AB. A holds lock 1 and requests lock 2, and B holds lock 2 and requests lock 1. (This situation will also occur in the exclusive lock of mysql, and the database will directly report an error message). There is another kind of deadlock in the thread pool: assuming that all worker threads in the thread pool are blocked while executing their respective tasks, they are waiting for the execution result of a certain task A. However, task A is in the queue and cannot be executed because there is no idle thread. In this way, all resources in the thread pool will be blocked forever, and a deadlock will occur.

•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, thus seriously affecting system performance.

•Concurrency error

The thread pool's work queue relies on the wait() and notify() methods to enable the worker thread to obtain tasks in time, 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 leaks

A serious risk of using thread pools is thread leaks. For a thread pool with a fixed number of worker threads, if a worker thread throws a RuntimeException or Error when executing a task, and these exceptions or errors are not caught, then the worker thread will terminate 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 to input data, but the user has not entered data, the thread has been blocked. Such a worker thread exists in name only, and it actually does not perform any tasks. If all threads in the thread pool are in this state, then the thread pool cannot add new tasks.

•Task overload

When there are a large number of tasks waiting to be executed in the worker thread queue, these tasks themselves may consume too many system resources and cause resource starvation.

To sum up, when using the thread pool, you must follow the following principles:

1. If task A needs to synchronously wait for the execution result of task B during execution, then task A is not suitable Add to the thread pool's work queue. If you add tasks like task A that need to wait for the execution results of other tasks to the queue, it may cause a deadlock

2. If the execution of a certain task may be blocked and it is blocked for a long time, you should Set a timeout to prevent the worker thread from being permanently blocked and causing thread leakage. In the server program, when the thread is waiting for the client to connect, or waiting for the data sent by the client, it may cause blocking. You can set the time in the following ways:

Call the setSotimeout method of ServerSocket to set the time to wait for the client to connect. overtime time.

For each socket connected to the client, call the setSoTImeout method of the socket to set the timeout period for waiting for the client to send data.

3. Understand the characteristics of the task and analyze whether the task performs io operations that often block or performs operations that never block. The former occupies the CPU intermittently, while the latter has higher utilization. Estimate how long it will take to complete the task, whether it is a short-term task or a long-term task, and then classify the tasks according to the characteristics of the tasks, and then add different types of tasks to the work queues of different thread pools, so that they can be Characteristics of tasks, allocation and adjustment of each thread pool

4. Adjust the size of the thread pool. The optimal size of the thread pool mainly depends on the number of available CPUs in the system and the characteristics of the tasks in the work queue. If there is only one work queue on a system with N CPUs, and all of them are computational (non-blocking) tasks, then when the thread pool has N or N+1 worker threads, the maximum CPU usage will generally be obtained. Rate.

If the work queue contains tasks that perform IO operations and are often blocked, the size of the thread pool should exceed the number of available CPUs, because not all worker threads are working all the time. Select a typical task, and then estimate the ratio WT/ST of the waiting time to the actual time occupied by the CPU for calculations in the project that performs this task. For a system with N CPUs, approximately N*(1+WT/ST) threads need to be set up to ensure that the CPU is fully utilized.

Of course, CPU utilization is not the only thing to consider when adjusting the thread pool. As the number of thread pool jobs increases, you will also encounter limitations in memory or other resources, such as sockets, open File handle or number of database connections, etc. It is necessary to ensure that the system resources consumed by multiple threads are within the range that the system can bear.

5. Avoid task overload. The server should limit the number of concurrent client connections based on the system's carrying capacity. When the client's connection exceeds the limit, the server can reject the connection and give a friendly prompt, or limit the queue length.

The above are several implementation methods of Java thread pool and the content of FAQs. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn