前言
在编程中,我们经常需要使用线程来异步处理任务。然而,每个线程的创建和销毁都需要一定的开销。如果每次执行一个任务都需要创建一个新的线程,那么这些线程的创建和销毁将消耗大量的资源。此外,线程之间是相互独立的,很难对其进行控制。因此,为了解决这些问题,我们需要使用线程池来对线程进行管理。在Java 1.5中,提供了Executor框架用于任务的提交和执行解耦。任务的提交交给Runnable或Callable,而Executor框架负责处理任务。Executor框架中最核心的成员是ThreadPoolExecutor,它是线程池的核心实现类。
1. ThreadPoolExecutor
要创建一个线程池,可以使用ThreadPoolExecutor类。ThreadPoolExecutor类一共有4个构造方法。其中,拥有参数最多的构造方法如下所示:
```java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
```
这些参数的作用如下:
- corePoolSize:核心线程数。默认情况下,线程池是空的,只有在任务提交时才会创建线程。如果当前运行的线程数少于corePoolSize,则创建新线程来处理任务;如果当前运行的线程数等于或多于corePoolSize,则不再创建新线程。如果调用线程池的prestartAllCoreThreads方法,则线程池会提前创建并启动所有的核心线程来等待任务。
- maximumPoolSize:线程池允许创建的最大线程数。如果任务队列已满,并且当前运行的线程数小于maximumPoolSize时,则线程池仍然会创建新的线程来处理任务。
- keepAliveTime:非核心线程的空闲时间。当非核心线程的数量超过corePoolSize时,多余的非核心线程在空闲时间内会被回收。keepAliveTime参数表示非核心线程在空闲状态下等待新任务的最长时间。当这个时间到达后,多余的非核心线程将被销毁。
- unit:keepAliveTime参数的时间单位。
- workQueue:用于存储待处理任务的阻塞队列。
- threadFactory:用于创建新线程的工厂。
- handler:当任务无法提交给线程池时使用的拒绝策略。
通过以上参数,我们可以灵活地配置线程池的行为,以满足不同的需求。
线程池的种类有以下四种:FixedThreadPool、CachedThreadPool、SingleThreadPool和ScheduledThreadPool。
- FixedThreadPool:固定数量的线程池,当任务提交时,线程池会启动一个新线程执行任务,直到达到最大线程数。
- CachedThreadPool:可缓存的线程池,当任务提交时,如果线程池中没有空闲线程,则会创建一个新线程执行任务;如果线程池已达到最大线程数,则会将任务放入队列中等待执行。
- SingleThreadPool:单线程化的线程池,只允许一个线程在执行任务,其他任务需要等待。
- ScheduledThreadPool:定时执行的线程池,可以在指定的延迟后执行任务或者定期执行任务。
通过直接或间接配置ThreadPoolExecutor的参数,可以创建不同类型的线程池。其中有四种常用的线程池:FixedThreadPool、CachedThreadPool、SingleThreadExecutor和ScheduledThreadPool。
3.1 FixedThreadPool
FixedThreadPool是一个可重用固定线程数的线程池。在Executors类中提供了创建FixedThreadPool的方法,如下所示:
```java
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory);
}
```
FixedThreadPool的corePoolSize和maximumPoolSize都设置为创建FixedThreadPool指定的参数nThreads,这意味着FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime设置为0L意味着多余线程会被立即终止。由于不会产生多余线程,所以keepAliveTime是无效的参数。另外,任务队列采用了无界阻塞队列LinkedBlockingQueue(容量默认为Integer.MAX_VALUE)。
3.2 CachedThreadPool
CachedThreadPool是一个根据需要创建线程的线程池。创建CachedThreadPool的代码如下:
```java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
}
```
CachedThreadPool会根据需要创建新的核心线程,当核心线程都在执行任务时,如果有新的任务提交进来,就会创建新的非核心线程来处理任务。当所有核心线程都在执行任务时,如果还有新的任务提交进来,就会将任务放入到任务队列中等待执行。当某个核心线程空闲时间超过60秒时,该线程会被回收。由于缓存策略是基于最近最少使用算法(LRU),因此缓存区的大小是动态变化的。
```java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
}
```
CachedThreadPool的corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE,这意味着CachedThreadPool没有核心线程,非核心线程是无界的。keepAliveTime设置为60L,则空闲线程等待新任务的最长时间为60s。在此用了阻塞队列SynchornousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个线程的移除操作都等待另一个线程的插入操作。
当执行executor方法时,首先会执行SynchronousQueue方法的offer方法来提交任务,并且查询线程池中是否有空闲的线程执行SynchronousQueue的poll方法来移除任务。如果有则配对成功,将任务交给这个空闲的线程处理;如果没有,则配对失败,创建新的线程去处理任务。当线程池中的线程空闲时,它会执行SynchoronousQueue的poll方法,等待SynchronousQueue中新提交的任务。如果超过60s没有新任务提交到SynchronousQueue,则这个空闲线程将终止。因为maximumPoolSize是无界的,所以如果提交的任务大于线程池中线程处理任务的速度,就会不断创建新线程。另外,每次提交任务都会立即有线程去处理。所以,CachedThreadPool比较适于大量的需要立即处理并且耗时较少的任务。
SingleThreadExecutor是一个单线程的执行器,它只有一个核心线程。当执行execute方法时,如果当前运行的线程数未达到核心线程数,也就是当前没有运行的线程,则创建一个新线程来处理任务。如果当前有运行的线程,则将任务添加到阻塞队列LinkedBlockingQueue中。因此,SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行。
ScheduledThreadPool是一个能实现定时和周期性任务的线程池。它的创建源码如下所示:
```java
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
```
这里创建了ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,它主要用于在给定延时之后运行任务或者定期处理任务。ScheduledThreadPollExecutor的构造方法如下:
以下是重构后的内容:
ScheduledThreadPoolExecutor构造方法的重构:
```java
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, 2147483647, 10L, TimeUnit.MILLISECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue());
}
```
从上述代码中可以看出,ScheduledThreadPoolExecutor的构造方法最终调用的是ThreadPoolExecutor的构造方法。其中,corePoolSize参数代表核心线程数,而maximumPoolSize参数的值为Integer.MAX_VALUE,因为这里采用的DelayedWorkQueue是无界的,所以maximumPoolSize参数是无效的。
当执行ScheduledThreadPoolExecutor的scheduleAtFixedRate或者scheduleWithFixedDelay方法时,会向DelayedWorkQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask(任务的包装类)。在添加任务之前,会检查当前运行的线程数是否达到了corePoolSize。如果没有达到核心线程数,则会新建线程并启动它,但并不是立即去执行任务而是去DelayedWorkQueue中取ScheduledFutureTask,然后执行任务。如果当前运行的线程数已经达到了corePoolSize,则将任务添加到DelayedWorkQueue中。需要注意的是,DelayedWorkQueue会将任务进行排序,先要执行的任务放在队列的前面。
与之前介绍的线程池不同之处在于,当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间,并将其放回DelayedWorkQueue中。这样可以确保在延迟时间到达之后再次执行该任务。
总结:
以上就是对ScheduledThreadPoolExecutor构造方法进行重构的内容。通过了解ScheduledThreadPoolExecutor的特点和工作原理,我们可以更好地理解其构造方法的设计以及如何使用该类来实现定时任务的调度。对于想要成为架构师或突破20~30K薪资范畴的人来说,不仅要局限于编码和业务领域,还需要具备良好的选型、扩展和编程思维能力。同时,良好的职业规划、学习习惯和持之以恒的精神也是非常重要的关键点。
如果你没有明确的方向,那么这里为你推荐一套由阿里高级架构师编写的《Android八大模块进阶笔记》。这套笔记旨在帮助大家整理杂乱、零散、碎片化的知识,使其更加系统化,从而更高效地掌握Android开发的各个知识点。与我们平时接触到的碎片化内容相比,这份笔记的知识点更加系统化,更容易理解和记忆,并且是严格按照知识体系编排的。
为了让更多人受益于这份宝贵的资料,我们诚挚地邀请你一键三连支持。如果你需要使用其中的文中资料,只需扫描文末CSDN官方认证微信卡片即可免费领取。同时,我们还特别为大家准备了一个实用的功能:群里的ChatGPT机器人,它可以解答你在工作或技术方面遇到的问题。
总之,这套《Android八大模块进阶笔记》将为你提供一个清晰的学习路径,帮助你更好地掌握Android开发技巧。希望你能够善用这份资料,不断提升自己的技能水平。