Home  >  Article  >  Java  >  A detailed explanation of the use and principles of thread pools in Java

A detailed explanation of the use and principles of thread pools in Java

黄舟
黄舟Original
2017-10-09 09:58:261410browse

This article mainly introduces the relevant information on the use and principles of Java thread pool in detail. It has certain reference value. Interested friends can refer to

What is a thread pool? ?

We can use java to easily create a new thread, and the operating system also costs a lot of money to create a thread. Therefore, based on the reuse of threads, the concept of thread pool is proposed. We use the thread pool to create several threads. After executing a task, the thread will exist for a period of time (the user can set the survival time of the idle thread, which will be discussed later). Introduction), when a new task comes, the idle thread will be directly reused, thus eliminating the loss of creating and destroying threads. Of course, idle threads will also be a waste of resources (only idle thread survival time is limited), but it is much better than frequently creating and destroying threads.
The following is my test code


  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }


  /**
   *@TODO 手动创建线程测试 
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");


  }

It takes about 400ms to run 5w threads using the thread pool, and it takes about 4350ms to run without using the thread pool. The efficiency is evident. (Readers can test it by themselves, but due to different computer configurations, the data will be different, but using the thread pool is definitely faster than creating threads).

How does Java use the thread pool?

The thread pool has been used in the above test code, and it will be formally introduced below.

The top level of all thread pools in java is an Executor interface, which has only one execute method, which is used to perform all tasks. Java also provides the ExecutorService interface, which inherits from Executor and expands the methods. The following is AbstractExecutorService is an abstract class that implements ExecutorService. Finally, ThreadPoolExecutor inherits from the above abstract class. The java thread pool we often use is an instance of this class created.

The Executors we used above are a tool class. It is a syntax sugar that encapsulates the thread pool parameters of various businesses for us and performs new operations.


 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue());
  }

The above is the source code of Executors.newFixedThreadPool(10).

The next point is here, let’s talk about the meaning of each parameter of the ThreadPoolExecutor construction method.


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)

The above construction method is the most comprehensive.

Below we will explain the meaning of some parameters based on the source code, which will be more convincing.

The following is the ThreadPoolExecutor#execute method, which is the actual executor of execute called by the interface above.


 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      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);
  }

ctl is an AtomicInteger instance, a class that provides CAS operations for atomic statements. It is used to record the number of threads currently running in the thread pool plus -2 ^29, the workCountOf method obtains its absolute value (you can see how to implement it in the source code). When it is less than corePoolSize, the addWorker method will be called (it is used to create a new Workder, and the Workder will create a Thread, so it is the method to create a thread. ), addWorkd will compare it with the value of corePoolSize or maxnumPoolSize during the thread creation process (when true is passed in, it will be compared based on corePoolSize, false will be compared based on maxnumPoolSize, and if the value is greater than or equal to its value, the creation will fail). It can be seen that if the number of currently running threads is less than corePoolSize, it is created and will be created successfully (
Only briefly discusses the thread pool in the Running state).

If the number of running threads is greater than or equal to corePoolSize, enter the second if, isRunning is compared with SHUTDOWN (its value = 0), as mentioned before, c is equal to the number of currently running threads plus -2^ 29. If the currently running thread data reaches 2^29, its value = 0, isRunning returns false, and executing addWorkd in else will also return false (addWorkd also checks it), so this means that the thread pool can Supports 2^29 threads running simultaneously (more than enough).

workQueue.offer(command) is to add the runnable to the waiting queue. After joining the waiting queue, the runWorker method will obtain the task execution from the queue. If the current queue uses a bounded queue (ArrayBlockingQueue), when the queue is full, the offer will return false. This will enter the else if. Look! False is passed in here, indicating that it is compared with maxnumPoolSize. If the thread running here If the number is greater than or equal to maxnumPoolSize, then this thread task will be rejected by the thread pool and execute reject(command). The rejection method uses the RejectedExecutionHandler (rejection strategy) in our ThreadPoolExecutor construction method, which will be explained in detail later.

After the above introduction combined with the source code, the following introduction to the parameters of our ThreadPoolExecutor will be easy to understand.

Thread creation and rejection strategies in the thread pool

corePoolSize, maxnumPoolSize, and BlockingQueue should be discussed together

When the threads running in the thread pool are less than corePoolSize, a new thread task will always create a new thread for execution; when it is greater than corePoolSize, the task will be added to the waiting queue blockingQueue. If the BlockingQueue you pass in is an unbounded Queue (LinkedBlockingQueue) This is a queue that can store "infinitely many" tasks. All of them will always be added to the queue successfully. It has nothing to do with maxnumPoolSize. This also means that the maximum number of threads in the thread pool is corePoolSize; but if you pass in Bounded queue (ArrayBlockingQueue, SynchronousQueue), when the queue is full and the number of threads is less than maxmunPoolSize, new threads are created until the number of threads is greater than maxnumPoolSize; if the number of threads is greater than maxnumPoolSize, the joining task will be rejected by the thread pool.

RejectedExecutionHandler rejection strategy Java implements 4 AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, and DiscardPolicy. Users can also implement this interface to implement their own rejection strategy; the first one is to throw an exception directly, and we can trycatch it; The second is to run the new task directly; the third is to cancel the oldest task in the queue; and the fourth is to cancel the current task.

The above is the detailed content of A detailed explanation of the use and principles of thread pools in Java. For more information, please follow other related articles on the PHP Chinese website!

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