1. 手动创建
线程池可以通过ThreadPoolExecutor
类进行手动配置和创建。在创建线程池时,需要设置7个关键参数,这些参数允许自由地配置线程池的行为:
- 核心线程数:定义线程池中始终保持的线程数量。
- 最大线程数:定义线程池允许达到的最大线程数量。
- 空闲非核心线程存活时间:定义非核心线程在闲置后保留在池中的时间。
- 空闲非核心线程存活时间单位:定义空闲非核心线程存活时间的单位。
- 阻塞队列:用于存储等待执行的任务的队列。
- 阻塞队列满后拒绝机制:定义当阻塞队列满后,如何处理新任务。
- 创建线程的工厂:用于创建新线程的工厂,可以自定义线程的名称、优先级等属性。
推荐使用手动创建方式,它提供更大的灵活性和控制力。
2. 自动创建
您也可以通过Executors
工具类的静态方法实现线程池的自动创建。例如:
Executors.newFixedThreadPool()
:创建一个具有固定数量的线程池。Executors.newCachedThreadPool()
:创建一个具有缓存线程池的线程池。Executors.newSingleThreadExecutor()
:创建一个单线程化的Executor,该Executor将按照顺序执行每个任务。Executors.newScheduledThreadPool()
:创建一个可调度的线程池。
自动创建方式本质上是通过创建ThreadPoolExecutor
实例实现的,而工具类会设置相应的参数。使用自动创建方式可以简化代码,但不如手动配置灵活。
3. 执行流程
- 创建线程池:首先需要创建一个线程池对象,可以通过手动创建ThreadPoolExecutor
- 提交任务:一旦线程池创建成功,就可以向线程池提交任务。任务可以是实现了Runnable接口或Callable接口的对象。线程池会根据任务的类型进行处理。
- 任务调度:线程池会根据任务的类型进行调度。如果当前工作线程数小于核心线程数,则直接创建新的核心工作线程去执行任务。如果工作线程数已经等于核心线程数,则新任务会被添加到阻塞队列中等待执行。如果队列已满,则会尝试创建一个新的工作线程去执行任务。如果当前线程的数量已经达到了最大线程数时,当有新的任务提交过来时,会执行拒绝策略。
- 任务执行:工作线程会从队列中取出任务并执行任务,直到队列中的任务被全部执行完毕。当任务执行结束空闲的非核心线程超过空闲时间时会被释放,直到等于核心线程数并继续等待任务
- 关闭线程池:当所有的任务都被执行完毕后,需要关闭线程池,释放资源。可以通过调用线程池的shutdown()方法来关闭线程池。
4. 参数详解
-
核心线程数(Core Pool Size):
- 描述:线程池中始终保持的线程数量,即使这些线程处于空闲状态。
- 默认值:
0
(无核心线程)。 - 示例:如果设置为
5
,则线程池始终保持5个活跃线程,即使它们处于空闲状态。
-
最大线程数(Maximum Pool Size):
- 描述:线程池允许达到的最大线程数量。当队列满后,如果已创建的线程数小于最大线程数,线程池会创建新线程执行任务。
- 默认值:
Integer.MAX_VALUE
。 - 示例:如果设置为
10
,则最多可以同时执行10个任务,即使队列中还有任务等待。
-
空闲非核心线程存活时间(Keep Alive Time):
- 描述:非核心线程闲置后保留在池中的时间。
- 默认值:非核心线程不保留。
- 示例:如果设置为
60
(秒),非核心线程在闲置60秒后会被终止。
-
空闲非核心线程存活时间单位(Time Unit):
- 描述:空闲非核心线程存活时间的单位。
- 默认值:
TimeUnit.SECONDS
。 - 可选值:
TimeUnit.NANOSECONDS
,TimeUnit.MICROSECONDS
,TimeUnit.MILLISECONDS
,TimeUnit.SECONDS
,TimeUnit.MINUTES
,TimeUnit.HOURS
,TimeUnit.DAYS
。
-
阻塞队列(Blocking Queue):
- 描述:用于存储等待执行的任务的队列。
- 默认值:无界队列(
new LinkedBlockingQueue<>()
)。 - 可选值:如
ArrayBlockingQueue
,LinkedBlockingQueue
,PriorityBlockingQueue
,DelayQueue
,SynchronousQueue
等。
-
阻塞队列满后拒绝机制(Rejected Execution Handler):
- 描述:当阻塞队列满后,处理新任务的策略。
- 默认值:抛出
RejectedExecutionException
。 - 可选策略:如
ThreadPoolExecutor.AbortPolicy
,ThreadPoolExecutor.CallerRunsPolicy
,ThreadPoolExecutor.DiscardPolicy
,ThreadPoolExecutor.DiscardOldestPolicy
。
-
创建线程的工厂(ThreadFactory):
- 描述:用于创建新线程的工厂。
- 默认值:使用系统默认的线程工厂。
- 可选值:自定义实现,用于设置新线程的名称、优先级等属性。
5.拒绝机制种类详解
-
ThreadPoolExecutor.AbortPolicy:
- 当阻塞队列满并且已创建的线程数达到最大线程数时,该策略会抛出
RejectedExecutionException
。 - 这是默认的拒绝策略。
- 当阻塞队列满并且已创建的线程数达到最大线程数时,该策略会抛出
-
ThreadPoolExecutor.CallerRunsPolicy:
- 当任务被拒绝时,该策略会调用执行器的
execute
方法在当前线程中直接运行任务。 - 也就是那个线程给的就还给哪个线程去执行
- 当任务被拒绝时,该策略会调用执行器的
-
ThreadPoolExecutor.DiscardPolicy:
- 当任务被拒绝时,该策略会简单地丢弃任务,不会执行任何特殊的操作。
-
ThreadPoolExecutor.DiscardOldestPolicy:
- 当阻塞队列满并且已创建的线程数达到最大线程数时,该策略会丢弃队列中最老的任务,然后尝试重新提交当前任务。
6.阻塞队列详解
-
ArrayBlockingQueue:
- 这是基于数组的有界阻塞队列。
- 它维护了一个定长的数组,以便缓存队列中的数据对象。
- 它内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
- 生产者和消费者在操作时,会共用同一个锁对象,这使得两者无法真正并行运行。
- 它完全可能采用分离锁,从而实现生产者和消费者操作的完全并行运行。
- 它的数据写入和获取操作已经足够轻巧,引入独立的锁机制除了给代码带来额外的复杂性外,在性能上完全占不到任何便宜。
- 当插入或删除元素时,它不会产生或销毁任何额外的对象实例。
- 它在创建时,可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
- 它的数据结构简单,处理速度快,适合用在多线程间传递数据,或作为线程池的元素传递通道。
-
LinkedBlockingQueue:
- 这是基于链表的有界阻塞队列。
- 当队列满时,新元素会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,对于 GC 的影响还是存在一定的区别。
- 它同样可以用于生产者-消费者模型中,生产者将产品放入队列,消费者从队列中取出产品。如果队列满了,生产者线程会被阻塞直到队列有空余空间;如果队列空了,消费者线程会被阻塞直到队列有可用的产品。
- 它通常用在并发编程中,作为线程池的工作队列,可以存储等待执行的任务。
-
PriorityBlockingQueue:
- 这是一个支持优先级排序的无界阻塞队列。
- 它内部实现使用了优先级队列,元素取出顺序按照优先级排序。如果优先级相同,则按照 FIFO 原则处理。
- 它适用于需要按照优先级处理任务的场景,例如任务调度系统、垃圾回收器等。
-
DelayQueue:
- 这是一个使用优先级队列实现的无界阻塞队列。
- 它存储的元素必须实现 Delayed 接口,元素只有在延迟期结束之后才能被取出。这使得 DelayQueue 适用于处理需要在特定时间点执行的任务,例如定时任务、计划任务等。
-
SynchronousQueue:
- 这是一个每次只存一个元素的无界阻塞队列。
- 只有当队列为空时,添加元素的线程才会被阻塞;只有当队列满时,移除元素的线程才会被阻塞。这意味着每次存取操作都会直接将数据从一个线程传递给另一个线程,没有中间环节。
- 它通常用于实现生产者-消费者模型,当生产者产生一个数据后直接交给消费者处理,或者当消费者需要一个数据时直接从生产者那里获取。这种模式可以减少数据在队列中的等待时间,提高效率。
-
LinkedTransferQueue:
- 这是一个基于链表的无界阻塞队列。
- 它支持在多个生产者和消费者之间进行公平的元素传输,保证了每个元素在传输过程中只有一个线程访问它。这避免了死锁和活锁的发生。
- 它通常用于并发编程中,例如在多线程程序中作为线程池的工作队列使用。它可以处理大量并发任务并且保证任务的公平调度。
-
LinkedBlockingDeque:
- 这是一个基于链表的双向阻塞队列。
- 它支持在两端添加和移除元素的操作,并且在没有可用空间时阻塞插入操作,在没有元素时阻塞移除操作。这使得它在需要在一端添加元素而在另一端移除元素的场景中非常有用,例如在环形缓冲区、队列和双端队列等数据结构中。
-
ConcurrentLinkedQueue:
- 这是一个无界的非阻塞队列(基于链表)。它被设计为线程安全的并且吞吐量高于
LinkedBlockingQueue
。它是无界的并且没有实现size()
方法,因此不适合作为缓存使用。它也不支持add(e)
和remove(o)
方法。它的操作都是非阻塞的,因此在高并发环境下性能表现较好。适用于多线程环境中的生产者-消费者模型等场景。 *ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
等都是线程安全的阻塞队列实现类。这些类提供了丰富的API来控制阻塞队列的行为和特性,例如设置队列容量、设置公平性策略等。这些类在Java并发编程中非常常见,常用于实现生产者-消费者模型、线程池等场景中