一次性说清楚 JAVA的 ThreadPoolExecutor 、newFixedThreadPool 和newCachedThreadPool 等

目录

1、Executors.newCachedThreadPool() 与 Executors.newFixedThreadPool(n) 的区别是什么?

2、Executors.newCachedThreadPool() 与 Executors.newFixedThreadPool(n) 可以调参吗,比如线程大小,线程等待时间 等等

3、newCachedThreadPool 与 ThreadPoolExecutor 是什么关系?

4、newFixedThreadPool 与 ThreadPoolExecutor 是什么关系?

5、通过自定义 ThreadPoolExecutor 类可以实现更多的灵活性。

6、newCachedThreadPool() 会堵塞吗?队列会满吗?  

7、ExecutorService 与 ThreadPoolExecutor 有什么区别?

8、ExecutorService还提供了一些工厂方法,除了newFixedThreadPool()和 newCachedThreadPool()  还有其他哪些?


1、Executors.newCachedThreadPool() 与 Executors.newFixedThreadPool(n) 的区别是什么?

Executors.newCachedThreadPool()Executors.newFixedThreadPool(2)都是创建线程池的工厂方法,但它们之间有几个重要的区别。

  1. 线程池大小

newCachedThreadPool()创建一个可缓存的线程池,线程池的大小根据需要自动调整,可以创建任意数量的线程。当需要执行任务时,线程池中没有可用线程时,它会创建新的线程来执行任务。如果线程池中的线程在60秒内没有被使用,则会被终止并从池中移除。因此,该方法适合执行大量短时间的任务。

newFixedThreadPool(n)创建一个固定大小的线程池,该线程池的大小为n。如果有更多的任务提交到线程池中,它们将会被放置在一个队列中等待执行,直到有可用的线程。如果队列已满,则会阻塞新任务的提交。因此,该方法适合执行长时间的任务或需要控制并发数的场景。

  1. 线程池的使用

newCachedThreadPool()适用于短期异步任务的执行,例如处理用户请求或发送电子邮件。由于它可以自动调整线程池的大小,因此它可以根据任务的数量动态分配系统资源。但是,由于它会创建大量的线程,因此如果长时间运行,可能会消耗过多的内存和处理器资源。

newFixedThreadPool(n)适用于长期执行的任务,例如服务器进程。由于它可以限制并发数,因此可以控制系统资源的使用。但是,如果任务数量过多,将可能导致队列阻塞和性能下降。

总之,选择使用newCachedThreadPool()newFixedThreadPool(n)取决于应用程序的需求和特点,需要根据实际情况进行选择。

2、Executors.newCachedThreadPool() 与 Executors.newFixedThreadPool(n) 可以调参吗,比如线程大小,线程等待时间 等等

Executors.newCachedThreadPool()Executors.newFixedThreadPool(n)方法都允许通过传递不同的参数来进行调整。具体来说,可以通过以下方式进行调参:

  1. newCachedThreadPool()方法允许通过ThreadPoolExecutor构造函数传递参数来自定义线程池的配置,例如设置线程池的最小和最大线程数,线程的存活时间等等。示例代码如下:
ExecutorService executorService = new ThreadPoolExecutor(
  0, 
  Integer.MAX_VALUE, 
  60L, 
  TimeUnit.SECONDS, 
  new SynchronousQueue<Runnable>()
);
  1. newFixedThreadPool(n)方法允许通过ThreadPoolExecutor构造函数传递参数来自定义线程池的配置,例如设置线程池的最大线程数,队列的容量等等。示例代码如下:
ExecutorService executorService = new ThreadPoolExecutor(
  n, 
  n, 
  0L, 
  TimeUnit.MILLISECONDS, 
  new LinkedBlockingQueue<Runnable>()
);

通过使用ThreadPoolExecutor构造函数,我们可以在不同的场景中配置线程池的大小、等待时间、队列容量等,以满足应用程序的需求。需要注意的是,这些参数需要根据具体的场景进行设置,不当的设置可能会导致性能问题。

小小总结一下:

newCachedThreadPool() 方法实际上返回一个 ExecutorService 对象,其内部实现就是基于 ThreadPoolExecutor 的。而且,newCachedThreadPool() 方法中默认的配置与上述代码中的配置是相似的,只是一些参数被设置为默认值了。可以看到,在 ThreadPoolExecutor 的构造函数中,第一个参数是 corePoolSize,它的值为0,这表示线程池的核心线程数为0,而且线程数可以增加到 Integer.MAX_VALUE,这与 newCachedThreadPool() 方法中的默认配置一致。同时,线程的存活时间也被设置为60秒,用于控制非核心线程的存活时间。因此,我们可以说,newCachedThreadPool() 方法就是通过 ThreadPoolExecutor 来实现的,并且在默认情况下与上述代码的配置是相似的。newFixedThreadPool(n)也是同样的道理。

3、newCachedThreadPool 与 ThreadPoolExecutor 是什么关系?

newCachedThreadPool 实际上是 ThreadPoolExecutor 的一个快捷创建方式,它使用了默认的配置参数,创建了一个核心线程数为 0,最大线程数为 Integer.MAX_VALUE,线程空闲时间为 60 秒的线程池。因此,newCachedThreadPool 实际上是调用了 ThreadPoolExecutor 的构造函数,传入了上述默认参数,返回一个 ThreadPoolExecutor 对象。

如果需要更细粒度地控制线程池的参数,例如修改线程数、线程空闲时间等等,就需要直接使用 ThreadPoolExecutor 来创建线程池,并通过构造函数或者 setter 方法来设置参数。

4、newFixedThreadPool 与 ThreadPoolExecutor 是什么关系?

newFixedThreadPool 实际上是 ThreadPoolExecutor 的另一个快捷创建方式,它创建了一个固定大小的线程池,其中核心线程数和最大线程数均为指定的线程数,线程空闲时间为 0 秒,等待队列为无界队列(LinkedBlockingQueue)。因此,newFixedThreadPool 实际上是调用了 ThreadPoolExecutor 的构造函数,传入了上述参数,返回一个 ThreadPoolExecutor 对象。

同样地,如果需要更细粒度地控制线程池的参数,例如修改线程数、线程空闲时间、等待队列类型等等,就需要直接使用 ThreadPoolExecutor 来创建线程池,并通过构造函数或者 setter 方法来设置参数。

5、通过自定义 ThreadPoolExecutor 类可以实现更多的灵活性。

演示如何使用自定义的 ThreadPoolExecutor 类来实现一个线程池,该线程池具有如下特点:

  1. 线程池中的线程数在 1 到 5 之间进行动态调整;
  2. 如果线程池中的线程数少于 5 个,则新任务将会创建新的线程来执行;
  3. 如果线程池中的线程数大于等于 5 个,且队列未满,则新任务将会加入到队列中等待执行;
  4. 如果队列已满,则新任务将会被拒绝,并抛出 RejectedExecutionException 异常。
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

    public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
    }

    @Override
    protected void terminated() {
        super.terminated();
    }

    @Override
    public void execute(Runnable command) {
        if (getPoolSize() < 5) {
            // 如果线程池中的线程数少于 5 个,则创建新的线程来执行任务
            super.execute(command);
        } else {
            // 如果线程池中的线程数大于等于 5 个,且队列未满,则加入到队列中等待执行
            boolean added = false;
            try {
                added = getQueue().offer(command, 1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!added) {
                // 如果队列已满,则拒绝新任务,并抛出 RejectedExecutionException 异常
                throw new RejectedExecutionException();
            }
        }
    }

    public static void main(String[] args) {
        // 创建一个初始大小为 1,最大大小为 5,队列大小为 10 的线程池
        CustomThreadPoolExecutor executor = new CustomThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));

        // 提交 10 个任务
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    System.out.println("开始执行任务:" + taskId);
                    Thread.sleep(2000);
                    System.out.println("任务执行完成:" + taskId);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

在上面的例子中,我们继承了 ThreadPoolExecutor 类,并重写了其中的 execute 方法。在 execute 方法中,我们实现了上述的特性:

  1. 如果线程池中的线线程数少于 5 个,则创建新的线程来执行任务;
  2. 如果线程池中的线程数达到了 5 个,则将任务放入队列中等待执行;
  3. 如果队列已满,则创建新的线程来执行任务,直到线程数达到了最大值 10;
  4. 如果线程池中的线程数达到了最大值 10,则将任务交给拒绝策略来处理。可以将这种策略称为“先创建线程,然后进入队列",
  5. 最后触发拒绝策略”的策略。在这种策略下,可以根据实际情况合理设置线程池的参数,从而达到更好的效果。

6、newCachedThreadPool() 会堵塞吗?队列会满吗?  

newCachedThreadPool() 不会在任务队列满了时阻塞,因为它并不使用任务队列,而是在需要时动态地创建新线程来执行任务。

具体来说,newCachedThreadPool() 使用了 SynchronousQueue,这是一个没有容量限制的队列,但实际上并不存储任务。当新任务到达时,线程池会尝试创建一个新线程来执行该任务,而不是将任务放入队列等待执行。如果没有可用线程,它将创建一个新线程。这就意味着这个线程池会根据任务的数量和执行时间动态地创建和回收线程,以适应负载的变化。

因此,newCachedThreadPool() 不会因为队列满了而阻塞,但可能会在高负载情况下创建大量线程,需要谨慎使用以避免资源消耗过多。如果您需要更多的线程控制,可以考虑使用具有有界队列的线程池,例如 ThreadPoolExecutor,来限制线程数量并更好地管理任务排队。

7、ExecutorService 与 ThreadPoolExecutor 有什么区别?

ExecutorServiceThreadPoolExecutor都是Java中用于管理线程池的类,但它们有一些不同点。

ExecutorService是一个接口,它定义了提交任务、执行任务和关闭线程池等方法,具体的实现由ThreadPoolExecutor等类来完成。在使用ExecutorService时,用户只需关注接口中定义的方法,而无需关注底层的实现细节。

ThreadPoolExecutor则是一个具体的线程池实现类,它实现了ExecutorService接口。除了实现ExecutorService中定义的方法外,ThreadPoolExecutor还提供了一些额外的功能,例如线程池中任务的拒绝策略、线程池中空闲线程的存活时间等。

因此,可以认为ExecutorService是一个线程池的抽象,而ThreadPoolExecutor是一个线程池的具体实现。

另外,ExecutorService还提供了一些工厂方法,例如newFixedThreadPool()newCachedThreadPool()等,这些方法会根据用户的需求创建不同类型的线程池,使得用户可以更方便地使用线程池,而无需关注底层的实现细节。

8、ExecutorService还提供了一些工厂方法,除了newFixedThreadPool()和 newCachedThreadPool()  还有其他哪些?

除了 newFixedThreadPool()newCachedThreadPool() 方法之外,ExecutorService 接口还提供了以下几个工厂方法:

  1. newSingleThreadExecutor():返回一个只有一个线程的线程池。如果该线程异常终止,会创建一个新的线程来替代它。

  2. newScheduledThreadPool(int corePoolSize):返回一个定时执行任务的线程池。它的核心线程数为 corePoolSize,可以执行定时任务,周期性任务等。

  3. newWorkStealingPool():返回一个根据当前处理器数量创建并行线程的线程池。该线程池使用工作窃取算法来提高CPU利用率。

这些方法都是通过 ThreadPoolExecutor 实现的,提供了一些默认的配置,使得用户可以方便地创建不同类型的线程池。