Executors类创建四种常见线程池

线程池架构

Java里面线程池的顶级接口是Executor,Executor并不是一个线程池,只是一个执行线程的工具。真正的线程池接口是ExecutorService。

类/接口描述
ExecutorService真正的线程池接口
ScheduledExecutorService功能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
ThreadPoolExecutorExecutorService的默认实现
ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

Java通过Executors工厂类提供四种线程池,分别为:

  1. newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,否则新建线程。(线程最大并发数不可控制)
  2. newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool : 创建一个定时线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

创建一个统一的线程任务,方便测试四种线程池

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running...");
    }

}

newSingleThreadExecutor

public class SingleThreadExecutorTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        MyRunnable myRunnable = new MyRunnable();
        for (int i = 0; i < 5; i++) {
            executorService.execute(myRunnable);
        }

        System.out.println("线程任务开始执行");
        executorService.shutdown();
    }

}

输出结果

底层实现

/**
 * 核心线程池大小=1
 * 最大线程池大小为1
 * 线程过期时间为0ms
 * LinkedBlockingQueue作为工作队列
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

从参数可以看出来,SingleThreadExecutor 相当于特殊的 FixedThreadPool,它的执行流程如下:

  1. 线程池中没有线程时,新建一个线程执行任务
  2. 有一个线程以后,将任务加入阻塞队列,不停的加
  3. 唯一的这一个线程不停地去队列里取任务执行

SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行

newFixedThreadPool

public class FixedThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        MyRunnable myRunnable = new MyRunnable();
        for (int i = 0; i < 5; i++) {
            executorService.execute(myRunnable);
        }

        System.out.println("线程任务开始执行");
        executorService.shutdown();
    }

}

输出结果

底层实现

/**
 * 核心线程池大小=传入参数
 * 最大线程池大小为传入参数
 * 线程过期时间为0ms
 * LinkedBlockingQueue作为工作队列
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

可以看到,FixedThreadPool核心线程数最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。

此外 keepAliveTime 为 0,也就是多余的空余线程会被立即终止(由于这里没有多余线程,这个参数也没什么意义)。

而这里选用的阻塞队列是 LinkedBlockingQueue,使用的是默认容量 Integer.MAX_VALUE,相当于没有上限。

这个线程池执行任务的流程如下:

  1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
  2. 线程数等于核心线程数后,将任务加入阻塞队列
  3. 由于队列容量非常大,可以一直加
  4. 执行完任务的线程反复去队列中取任务执行

FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量

newCachedThreadPool

public class CachedThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        MyRunnable myRunnable = new MyRunnable();
        for (int i = 0; i < 5; i++) {
            executorService.execute(myRunnable);
        }

        System.out.println("线程任务开始执行");
        executorService.shutdown();
    }

}

输出结果

底层实现

/**
 *  核心线程池大小=0
 *  最大线程池大小为Integer.MAX_VALUE
 *  线程过期时间为60s
 *  使用SynchronousQueue作为工作队列
 */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

CachedThreadPool 没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收。

CachedThreadPool 使用的队列是 SynchronousQueue,这个队列的作用就是传递任务,并不会保存。

因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器

newScheduledThreadPool

public class ScheduledThreadPoolTest {

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        MyRunnable myRunnable = new MyRunnable();
        for (int i = 0; i < 5; i++) {
            // 参数1:目标对象,参数2:隔多长时间开始执行线程,参数3:执行周期,参数4:时间单位
            scheduledExecutorService.scheduleAtFixedRate(myRunnable, 1, 2, TimeUnit.SECONDS);
        }

        System.out.println("线程任务开始执行");
    }

}

输出结果

底层实现

/**
 * 核心线程池大小=传入参数
 * 最大线程池大小为Integer.MAX_VALUE
 * 线程过期时间为0ms
 * DelayedWorkQueue作为工作队列
 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

ScheduledThreadPoolExecutor 的执行流程如下:

  1. 添加一个任务
  2. 线程池中的线程从 DelayQueue 中取任务
  3. 然后执行任务

具体执行任务的步骤也比较复杂:

  1. 线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask

  2. 执行完后修改这个 task 的 time 为下次被执行的时间

  3. 然后再把这个 task 放回队列中

ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景

Executors和ThreaPoolExecutor创建线程池的区别

Executors 各个方法的弊端:

  1. newFixedThreadPool newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
  2. newCachedThreadPool newScheduledThreadPool: 主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

ThreaPoolExecutor

  1. 创建线程池方式只有一种,就是走它的构造函数,参数自己指定

两种提交任务的方法

ExecutorService 提供了两种提交任务的方法:

  • execute():提交不需要返回值的任务
  • submit():提交需要返回值的任务
execute

execute() 的参数是一个 Runnable,也没有返回值。因此提交后无法判断该任务是否被线程池执行成功

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
    @Override
    public void run() {
        //do something
    }
});
submit
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

submit() 有三种重载,参数可以是 Callable 也可以是 Runnable

同时它会返回一个 Funture 对象,通过它我们可以判断任务是否执行成功。

获得执行结果调用 Future.get() 方法,这个方法会阻塞当前线程直到任务完成

提交一个 Callable 任务时,需要使用 FutureTask 包一层:

FutureTask futureTask = new FutureTask(new Callable<String>() {    //创建 Callable 任务
    @Override
    public String call() throws Exception {
        String result = "";
        //do something
        return result;
    }
});
Future<?> submit = executor.submit(futureTask);    //提交到线程池
try {
    Object result = submit.get();    //获取结果
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

<think>好的,我现在需要回答用户关于Java常见线程池型及其用途的问题。首先,用户提到了线程池的核心概念和优势,引用了四个参考资料。我需要结合这些引用内容,整理出Java常见线程池型,并说明它们的特点和应用场景。 首先,根据引用[1],线程池通过ExecutorService实现,可以降低线程创建和销毁的开销。引用[3]详细解释了corePoolSize和maximumPoolSize的作用,以及线程池如何根据任务数量动态调整线程数量。引用[4]提到了不同线程池的适用场景,比如IO密集型、计算密集型和混合型任务,还提供了万能公式来计算corePoolSize。 接下来,我需要回忆Java中通过Executors提供的几种常见线程池: 1. **FixedThreadPool**:固定线程数的线程池。适用于负载较重的服务器,需要限制线程数量以避免资源耗尽。引用[3]提到corePoolSize和maximumPoolSize相等,所以这里应该是固定大小的。 2. **CachedThreadPool**:可缓存线程池,线程数可扩展。适合执行很多短期异步任务,或者负载较轻的服务器。当任务增加时,会创建新线程,空闲线程会被回收。这里需要确认是否maximumPoolSize是Integer.MAX_VALUE,队列是SynchronousQueue,如引用[4]提到的同步队列快速扩容。 3. **SingleThreadExecutor**:单线程的线程池,保证任务顺序执行。适用于需要顺序执行任务的场景,如日志记录。 4. **ScheduledThreadPool**:支持定时或周期性任务执行的线程池。适用于需要调度任务的场景,比如定时任务。 另外,用户还提到了ThreadPoolExecutor,这是线程池的基础实现,其他线程池都是通过配置它的参数来实现的。需要说明参数如corePoolSize、maximumPoolSize、keepAliveTime、workQueue等的影响。 应用场景部分需要结合引用[4]中的分,比如IO密集型使用CachedThreadPool,计算密集型使用FixedThreadPool,混合型使用自定义参数。还要提到线程池的配置建议,比如万能公式。 在回答结构上,需要分点列出每种线程池,说明其实现方式、参数配置、特点和应用场景。最后总结如何根据任务型选择合适的线程池,并提到线程池的配置建议。 需要确保使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,但根据用户提供的引用,这里可能不需要太多数学公式,更多是参数说明。所以可能只需在引用公式时使用,比如引用[4]中的公式。 最后生成相关问题,可能需要包括线程池参数配置、线程池的工作流程、如何避免资源耗尽、以及不同任务型的线程池选择等问题。 检查是否有遗漏的信息,比如引用[2]提到线程池流程,可能在回答中简要提及线程池的工作机制,但用户的问题集中在型和用途,所以可能不需要深入流程细节,但可以提到核心参数如队列型的影响。 现在组织语言,确保每个线程池型都清晰说明,并正确引用相关文献,如引用[3]解释corePoolSize和maximumPoolSize的关系,引用[4]说明参数配置公式等。</think>### Java常见线程池型及其应用场景 Java通过`java.util.concurrent.Executors`提供了多种线程池实现,每种型针对不同任务场景设计。以下为四种典型线程池及其用途: --- #### 1. **固定大小线程池(FixedThreadPool)** - **实现方式**: ```java Executors.newFixedThreadPool(int nThreads) ``` 核心线程数(`corePoolSize`)与最大线程数(`maximumPoolSize`)相等,任务队列使用无界的`LinkedBlockingQueue`[^3]。 - **特点**: - 线程数量固定,不会因任务激增而扩容。 - 任务队列无界,可能引发内存溢出(OOM)。 - **应用场景**: 适用于**计算密集型任务**(如复杂运算),或需严格控制并发数的场景(如高负载服务器)[^4]。 --- #### 2. **缓存线程池(CachedThreadPool)** - **实现方式**: ```java Executors.newCachedThreadPool() ``` 核心线程数为0,最大线程数为`Integer.MAX_VALUE`,任务队列为`SynchronousQueue`(直接传递任务,不存储)。 - **特点**: - 线程数量根据任务动态伸缩,空闲线程60秒后回收。 - 高并发时可能创建大量线程,导致资源耗尽。 - **应用场景**: 适用于**短时异步任务**或**IO密集型任务**(如HTTP请求处理),任务执行时间短且频率波动大[^4]。 --- #### 3. **单线程池(SingleThreadExecutor)** - **实现方式**: ```java Executors.newSingleThreadExecutor() ``` 仅1个核心线程,任务队列为无界`LinkedBlockingQueue`。 - **特点**: - 保证任务按提交顺序执行。 - 无界队列同样存在OOM风险。 - **应用场景**: 适用于需**顺序执行任务**的场景(如日志顺序写入、单线程GUI更新)[^1]。 --- #### 4. **定时任务线程池(ScheduledThreadPool)** - **实现方式**: ```java Executors.newScheduledThreadPool(int corePoolSize) ``` 使用`DelayedWorkQueue`支持延迟或周期性任务。 - **特点**: - 可调度任务在固定延迟或周期执行。 - 核心线程数固定,但空闲线程不会被回收。 - **应用场景**: 适用于**定时任务**(如数据定时同步、心跳检测)。 --- ### **ThreadPoolExecutor 参数配置** 自定义线程池需配置以下参数: - **`corePoolSize`**:核心线程数(长期保留的线程)。 - **`maximumPoolSize`**:最大线程数(突发任务时临时扩容)。 - **`workQueue`**:任务队列型(影响任务排队策略)。 - **`keepAliveTime`**:非核心线程空闲回收时间。 - **`threadFactory`**:线程创建工厂(可定制线程属性)。 - **`handler`**:拒绝策略(任务队列满时的处理方式)。 --- ### **线程池选择建议** - **IO密集型**(如网络请求):使用`CachedThreadPool`或自定义线程池,通过公式调整参数: $$ \text{corePoolSize} = N \times U \times (1 + \frac{W}{C}) $$ 其中$N$为CPU核数,$U$为目标CPU利用率(0.7~0.8),$W/C$为等待时间与计算时间的比值[^4]。 - **计算密集型**:使用`FixedThreadPool`,核心线程数设为CPU核数+1。 - **混合型任务**:结合队列型(如`ArrayBlockingQueue`)与动态扩容策略,避免任务堆积。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值