Java --- Java中的线程池

本文详细介绍了Java中创建线程池的两种方法:使用ThreadPoolExecutor和Executors。强调了阿里巴巴开发手册推荐使用ThreadPoolExecutor以更好地控制线程池行为,避免资源耗尽风险。对比了不同线程池类型的特性和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中,想要创建一个线程池有两种方式,分别是使用Executors的工厂方法创建直接使用ThreadPoolExecutor去创建一个线程池

在阿里巴巴开发手册中有讲,在有多线程开发的需求时,强制使用线程池,避免因为“过度切换”而引起的资源耗尽问题,并且创建线程池时需通过ThreadPoolExecutor的方式去创建。原文如下:

  1. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  2. 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明:Executors返回的线程池对象的弊端如下:
    1)FixedThreadPool和SingleThreadPool:
      允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
    2)CachedThreadPool和ScheduledThreadPool:
      允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

在此附上GitHub上阿里巴巴开发手册的链接:https://github.com/alibaba/p3c/blob/master/阿里巴巴Java开发手册(详尽版).pdf

所以,我们先来讲ThreadPoolExecutor创建线程池的方式。

一. ThreadPoolExecutor创建线程池

1. ThreadPoolExecutor的参数

先来看一下ThreadPoolExecutor的构造方法:

public ThreadPoolExecutor(int corePoolSize, 
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

下面对ThreadPoolExecutor构造方法中的参数进行逐一解释:

  • corePoolSize:核心线程数。当有新任务提交时,如果当前线程数 < 核心线程数,则创建一个线程
  • maximumPoolSize:最大线程数。当有新任务提交时,如果核心线程数 <= 当前线程数 < 最大线程数,且workQueue已满,则新建一个线程
  • keepAliveTime:保活时间。当空闲线程闲置时间达到保活时间后,则销毁线程(需要注意的是,当当前线程数 <= 核心线程数时,即使有线程空闲,也不会被销毁)
  • unit:保活时间的单位。常用的保活时间的值有:
    • TimeUnit.SECONDS
    • TimeUnit.MINUTES
    • TimeUnit.HOURS
    • TimeUnit.DAYS
  • workQueue:任务队列。当有新任务提交时,如果核心线程数 <= 当前线程数 < 最大线程数,但workQueue未满,就将当前任务加进任务队列中。(需要注意的是,在创建ThreadPoolExecutor时,传入的workQueue要指定大小,将在后面的例子中示例)。常用的workQueue的类型有:
    • ArrayBlockingQueue
    • LinkedBlockingQueue
  • threadFactory:线程工厂。用来创建线程
  • handler:任务拒绝策略。当前线程数 = 最大线程数,且任务队列已满,此时要是有新的任务,对新任务要使用拒绝策略去处理。关于任务拒绝策略的值有下面几种:
    • ThreadPoolExecutor.AbortPolicy:中止策略。线程池的默认决绝策略,当线程池无法处理新任务的时候,抛出RejectedExecutionException异常
    • ThreadPoolExecutor.DiscardPolicy:抛弃策略。当线程池无法处理新任务,则抛弃该任务
    • ThreadPoolExecutor.DiscardOldestPolicy:抛弃最旧策略。抛弃任务队列中入队最早的任务,然后尝试重新提交新任务
    • ThreadPoolExecutor.CallerRunsPolicy:调用者运行策略。将该新任务回退给调用者,由调用者所在的线程来运行任务。不会抛弃任务,也不会抛出异常。

2. ThreadPoolExecutor创建线程池的栗子:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 30
                , TimeUnit.MINUTES, new ArrayBlockingQueue<>(10), Thread::new, new ThreadPoolExecutor.AbortPolicy());

        Runnable r1 = () -> {
            Thread.currentThread().setName("thread 1 ");
            int i = 0;
            while (i++ < 10) {
                System.out.println("name: " + Thread.currentThread().getName() + ", count : " + i);
            }
        };
        
        Runnable r2 = () -> {
            Thread.currentThread().setName("thread 2 ");
            int i = 0;
            while (i++ < 10) {
                System.out.println("name: " + Thread.currentThread().getName() + ", count : " + i);
            }
        };
        
        threadPoolExecutor.execute(r1);
        threadPoolExecutor.execute(r2);
        threadPoolExecutor.shutdown();
   }
}

运行结果如下:

name: thread 1 , count : 1
name: thread 1 , count : 2
name: thread 1 , count : 3
name: thread 1 , count : 4
name: thread 2 , count : 1
name: thread 2 , count : 2
name: thread 2 , count : 3
name: thread 2 , count : 4
name: thread 2 , count : 5
name: thread 2 , count : 6
name: thread 1 , count : 5
name: thread 2 , count : 7
name: thread 1 , count : 6
name: thread 1 , count : 7
name: thread 1 , count : 8
name: thread 1 , count : 9
name: thread 2 , count : 8
name: thread 1 , count : 10
name: thread 2 , count : 9
name: thread 2 , count : 10

二. 使用Executors创建线程池

1. 通过Executors的工厂方法,可创建五种线程池:

  • newSingleThreadExecutor:单线程的线程池。coreSize = maxSize = 1,永远只有一个线程在工作,任务队列无限大。每完成一个任务就从任务队列中取一个,所有任务都能保证顺序执行。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
  • newFixedThreadPool:固定大小的线程池。coreSize = maxSize = n,每来一个任务就创建一个线程直至线程数达到n,此时新任务放进任务队列,线程每完成一个任务就从任务队列中取一个任务。这种线程池用于已知并发压力比较大的情况下,提前对线程数进行限制,以免线程开销耗尽系统资源。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
  • newCachedThreadPool:无限大的线程池。coreSize = 0;maxSize = Integer.MAX_VALUE;每来一个任务就创建一个线程,任务队列大小为0,保活时间为60秒,即所有线程闲置时间超过60秒都会被销毁,所以线程池存在线程为0的情况。这种线程池用于处理执行时间短的任务。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。关于这个线程池是Java 1.8新增的线程池,博主也没有用过,需要用到的同学可自行Google。

2. 使用Executors创建线程池的一个小栗子:

​public class ThreadPoolTest {
    public static void main(String[] args) {
        Runnable r1 = () -> {
            Thread.currentThread().setName("thread 1 ");
            int i = 0;
            while (i++ < 10) {
                System.out.println("name: " + Thread.currentThread().getName() + ", count : " + i);
            }
        };

        Runnable r2 = () -> {
            Thread.currentThread().setName("thread 2 ");
            int i = 0;
            while (i++ < 10) {
                System.out.println("name: " + Thread.currentThread().getName() + ", count : " + i);
            }
        };
​
        ExecutorService test = Executors.newSingleThreadExecutor();
        test.execute(r1);
        test.execute(r2);
        test.shutdown();
    }
}

运行结果:

name: thread 1 , count : 1
name: thread 1 , count : 2
name: thread 1 , count : 3
name: thread 1 , count : 4
name: thread 1 , count : 5
name: thread 1 , count : 6
name: thread 1 , count : 7
name: thread 1 , count : 8
name: thread 1 , count : 9
name: thread 1 , count : 10
name: thread 2 , count : 1
name: thread 2 , count : 2
name: thread 2 , count : 3
name: thread 2 , count : 4
name: thread 2 , count : 5
name: thread 2 , count : 6
name: thread 2 , count : 7
name: thread 2 , count : 8
name: thread 2 , count : 9
name: thread 2 , count : 10

最后,有关线程池中​ExecutorService submit和execute的区别参考这篇文章:https://blog.youkuaiyun.com/peachpi/article/details/6771946

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值