Home  >  Article  >  Java  >  What is Java thread pool? Detailed explanation of Java thread pool

What is Java thread pool? Detailed explanation of Java thread pool

不言
不言Original
2018-09-19 15:13:352105browse

This article brings you what is the Java thread pool? The detailed explanation of Java thread pool has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

1. What is a thread pool:

java.util.concurrent.Executors provides an implementation of the java.util.concurrent.Executor interface for creating a thread pool

Multi-threading technology mainly solves the problem of multiple thread execution in the processor unit. It can significantly reduce the idle time of the processor unit and increase the throughput capacity of the processor unit.​

Assume that the time required for a server to complete a task is: T1 time to create a thread, T2 time to execute the task in the thread, and T3 time to destroy the thread.

If: T1 T3 is much larger than T2, you can use a thread pool to improve server performance.

A thread pool includes the following four basic components:

  • 1. Thread pool manager (ThreadPool): used to create and manage Thread pool, including creating thread pool, destroying thread pool, adding new tasks;

  • 2. Worker thread (PoolWorker): Threads in the thread pool are in a waiting state when there are no tasks. Cyclic execution of tasks;

  • 3. Task interface (Task): The interface that each task must implement in order for the worker thread to schedule the execution of the task. It mainly stipulates the entry of the task. The finishing work after the task is executed, the execution status of the task, etc.;

  • 4. Task Queue (taskQueue): used to store unprocessed tasks. Provide a buffering mechanism.

Thread pool technology focuses on how to shorten or adjust the T1 and T3 times, thereby improving the performance of the server program. It arranges T1 and T3 respectively in the startup and end time periods of the server program or some idle time periods, so that when the server program processes customer requests, there will be no overhead of T1 and T3.

The thread pool not only adjusts the time period during which T1 and T3 are generated, but it also significantly reduces the number of threads created. Let’s look at an example:

Assume a server has one day There are 50,000 requests to handle, and each request requires a separate thread to complete. In the thread pool, the number of threads is generally fixed, so the total number of threads generated will not exceed the number of threads in the thread pool. If the server does not use the thread pool to process these requests, the total number of threads will be 50,000. The general thread pool size is much less than 50,000. Therefore, the server program that uses the thread pool will not waste time in processing requests in order to create 50,000, thereby improving efficiency.

2. Common thread pool

①newSingleThreadExecutor

A single thread thread pool, that is, only one thread works in the thread pool at a time, and a single thread string Execute tasks in rows

②newFixedThreadExecutor(n)

A fixed number of thread pools. If a task is not submitted, it will be one thread until the maximum number of thread pools is reached, and then enter later Wait for the queue to continue execution until the previous task is completed

③newCacheThreadExecutor (recommended)

Cacheable thread pool,When the thread pool size exceeds the processing task If the required threads are needed, then some idle (usually no execution for 60 seconds) threads will be recycled. When a task comes, new threads will be intelligently added for execution.

④newScheduleThreadExecutor

Unlimited thread pool size, supports scheduled and periodic execution threads

The thread pool provided by java is more powerful , I believe that if you understand the working principle of the thread pool, you will not feel unfamiliar when you look at the thread pool in the class library.

What is Java thread pool? Detailed explanation of Java thread pool

What is Java thread pool? Detailed explanation of Java thread pool

##Article 2:

Instructions for using Java thread pool

1 Introduction

The use of threads occupies an extremely important position in java. In jdk1.4 and previous jdk versions, there are many information about thread pools. It is extremely crude to use. This situation has changed a lot after jdk1.5. After Jdk1.5, the java.util.concurrent package was added. This package mainly introduces the use of threads and thread pools in Java. It provides a lot of help for us to deal with thread issues in development.

2: Thread pool

The role of thread pool:

The function of the thread pool is to limit the number of execution threads in the system.
According to the system environment, the number of threads can be set automatically or manually to achieve the best operation effect; less will waste system resources, and more will cause system congestion and inefficiency. Use the thread pool to control the number of threads, and other threads are queued to wait. After a task is executed, the frontmost task in the queue is taken and execution begins. If there is no waiting process in the queue, this resource of the thread pool is waiting. When a new task needs to be run, if there are waiting worker threads in the thread pool, it can start running; otherwise, it enters the waiting queue.

Why use a thread pool:

1. Reduce the number of thread creation and destruction, each worker thread can be reused and can perform multiple tasks .

2. You can adjust the number of working threads in the thread pool according to the system's capacity to prevent the server from being exhausted due to excessive memory consumption (each thread requires about 1MB of memory, and the thread starts The more memory is consumed, the more memory will be consumed and eventually the system will crash).

The top-level interface of the thread pool in Java is Executor, but strictly speaking, Executor is not a thread pool, but just a tool for executing threads. The real thread pool interface is ExecutorService.

Several important categories:

Classification Function
ExecutorService Real thread pool interface.
ScheduledExecutorService can be similar to Timer/TimerTask and solve problems that require repeated execution of tasks.
ThreadPoolExecutor Default implementation of ExecutorService.
ScheduledThreadPoolExecutor Inherits the ScheduledExecutorService interface implementation of ThreadPoolExecutor, a class implementation of periodic task scheduling.

Настроить пул потоков относительно сложно, особенно если принцип пула потоков не очень ясен, весьма вероятно, что настроенный пул потоков не является оптимальным, поэтому в классе Executors предусмотрены некоторые статические фабрики. Создайте несколько часто используемых пулов потоков.

1.newSingleThreadExecutor

Создание однопоточного пула потоков. В этом пуле потоков работает только один поток, что эквивалентно одному потоку, выполняющему все задачи последовательно. Если единственный поток заканчивается ненормально, его заменит новый поток. Этот пул потоков гарантирует, что все задачи выполняются в том порядке, в котором они отправлены.

2.newFixedThreadPool

Создайте пул потоков фиксированного размера. Поток создается каждый раз при отправке задачи, пока поток не достигнет максимального размера пула потоков. Размер пула потоков остается неизменным после достижения максимального значения.Если поток завершается из-за исключения выполнения, пул потоков будет пополнен новым потоком.

3.newCachedThreadPool

Создайте кэшируемый пул потоков. Если размер пула потоков превышает потоки, необходимые для обработки задач,

то некоторые простаивающие потоки (не выполняющие задачи в течение 60 секунд) будут перезапущены. Когда количество задач увеличится, этот пул потоков можно будет добавить разумно Новый поток для решения этой задачи. Этот пул потоков не ограничивает размер пула потоков.Размер пула потоков полностью зависит от максимального размера потока, который может создать операционная система (или JVM).

4.newScheduledThreadPool

Создайте пул потоков неограниченного размера. Этот пул потоков поддерживает синхронизацию и периодическое выполнение задач.

Instance

1: newSingleThreadExecutor

package com.thread;
 /* * 
  *通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class MyThread extends Thread { 

    @Override public void run() { // TODO Auto-generated method stub // super.run();
    System.out.println(Thread.currentThread().getName()+"正在执行....");
    } 
}
package com.thread; 
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; 
/* 
 * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class singleThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newSingleThreadExecutor(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}

Result:

pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....

2newFixedThreadPool

 package com.thread; 
import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors; 
/* * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class fixedThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newFixedThreadPool(2); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}

Результат:

pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-2正在执行....

3, newCachedThreadPool

package com.thread;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 /* 
 * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class cachedThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newCachedThreadPool(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}

Результат:

pool-1-thread-2正在执行....
pool-1-thread-1正在执行....
pool-1-thread-3正在执行....
pool-1-thread-4正在执行....
pool-1-thread-5正在执行....

4, newScheduledThreadPool

package com.thread; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; 
/* * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class scheduledThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub
 ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(1);
       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间就触发异常
 @Override public void run() { // TODO Auto-generated method stub //throw new RuntimeException();
            System.out.println("===================");

        }}, 1000, 5000, TimeUnit.MILLISECONDS);  

       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的
 @Override public void run() { // TODO Auto-generated method stub
 System.out.println(System.nanoTime());

        }}, 1000, 2000, TimeUnit.MILLISECONDS);

    }

}

Результат:

===================
23119318857491
23121319071841
23123319007891
===================
23125318176937
23127318190359
===================
23129318176148
23131318344312
23133318465896
===================
23135319645812

Три: Подробное объяснение ThreadPoolExecutor

Сигнатура полного метода построения ThreadPoolExecutor: ThreadPoolExecutor (int corePoolSize, int MaximumPoolSize, long KeepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory, обработчик RejectedExecutionHandler)</runnable> .

##corePoolSize - количество потоков, сохраненных в пул, включая простаивающие потоки.

maximumPoolSize — максимальное количество потоков, разрешенное в пуле.

keepAliveTime — когда число потоков превышает количество ядра, это максимальное время, в течение которого лишние простаивающие потоки ждут новых задач перед завершением.

unit — единица времени параметра KeepAliveTime.

workQueue — очередь, используемая для хранения задач перед выполнением. Эта очередь содержит только выполняемые задачи, отправленные методом выполнения.

threadFactory — фабрика, используемая исполнителем для создания нового потока.

handler — обработчик, используемый, когда выполнение блокируется из-за превышения области действия потока и емкости очереди.

ThreadPoolExecutor — это базовая реализация класса Executors.

В справочном документе JDK есть такой отрывок:

"Программистам настоятельно рекомендуется использовать более удобный

Executorsфабричный метод Executors.newCachedThreadPool () (неограниченный пул потоков, возможна автоматическая перезапуск потоков), Executors.newFixedThreadPool(int) (пул потоков фиксированного размера) Executors.newSingleThreadExecutor() (один фоновый поток )

Все они имеют предопределенные настройки для большинства сценариев использования."

Исходный код нескольких классов представлен ниже:

ExecutorService newFixedThreadPool ( int nThreads): пул потоков фиксированного размера.

Вы можете видеть, что размеры corePoolSize и MaximumPoolSize одинаковы (на самом деле, как будет показано позже, параметр MaximumPoolSize не имеет смысла, если используется неограниченная очередь), а имена таблиц настроек KeepAliveTime и модуль Что? -Пришло время осознать, что ты не хочешь оставаться в живых! Последняя BlockingQueue выбрала LinkedBlockingQueue, особенностью которой является неограниченность.

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

      }</runnable>

ExecutorService newSingleThreadExecutor(): одиночный поток

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService   
                 (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()));   
       }</runnable>

ExecutorService newCachedThreadPool(): Неограниченный пул потоков, может выполнять автоматическую перезапуск потоков

Эта реализация интересна. Первый — это неограниченный пул потоков, поэтому мы можем обнаружить, что максимальный размер пула очень большой. Во-вторых, SynchronousQueue используется при выборе BlockingQueue. Возможно, вы немного незнакомы с этой BlockingQueue.Проще говоря: в этой QUEUE каждая операция вставки должна ожидать соответствующей операции удаления другого потока.

 public static ExecutorService newCachedThreadPool() {
   return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<runnable>());   
    }</runnable>
Начнем с параметра BlockingQueue workQueue. В JDK было очень ясно указано, что существует три типа очередей.

All BlockingQueue можно использовать для передачи и хранения отправленных задач. Эту очередь можно использовать для взаимодействия с размером пула:

如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)

如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

queue上的三种类型。

排队有三种通用策略:

直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

BlockingQueue的选择。

例子一:使用直接提交策略,也即SynchronousQueue。

首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。

我们使用一下参数构造ThreadPoolExecutor:

 new ThreadPoolExecutor( 2, 3, 30, TimeUnit.SECONDS, new  SynchronousQueue<runnable>(), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());</runnable>

当核心线程已经有2个正在运行.

  1. 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。

  2. 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。

  3. 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。

  4. 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。

所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。

例子二:使用无界队列策略,即LinkedBlockingQueue

这个就拿newFixedThreadPool来说,根据前文提到的规则:

如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢?

如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。

例子三:有界队列,使用ArrayBlockingQueue。

这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。

举例来说,请看如下构造方法:

 new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<runnable>(2), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());</runnable>

假设,所有的任务都永远无法执行完。

对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

keepAliveTime

jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。

什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。

合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。

RejectedExecutionHandler

另一种情况便是,即使向老板借了工人,但是任务还是继续过来,还是忙不过来,这时整个队伍只好拒绝接受了。

RejectedExecutionHandler接口提供了对于拒绝任务的处理的自定方法的机会。在ThreadPoolExecutor中已经默认包含了4中策略,因为源码非常简单,这里直接贴出来。

CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {

               r.run();

           }

       }

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException

 这种策略直接抛出异常,丢弃任务。

DiscardPolicy:不能执行的任务将被删除

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

       }

 这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
if (!e.isShutdown()) {

               e.getQueue().poll();

               e.execute(r);

           }

       }

该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。

设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。

总结:

keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。

反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。

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

The above is the detailed content of What is Java thread pool? Detailed explanation of Java thread pool. 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