Java线程池详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


线程池

相比于进程,线程的创建成本比较低,但也需进行空间的初始化等工作。线程池则提供了线程回收利用的途径,减少了线程创建、销毁带来的性能损失。
线程池,顾名思义就是一个存放线程的池子,当需要线程时可以从线程池中取出线程,当不需要线程时也不需要销毁线程,只需要将程还入线程池即可。线程池带来了以下的优点:

  • 通过线程的给出与收回实现了线程的重复利用,从而减少了线程频繁创建与销毁带来的性能损耗。
  • 获得线程不需要创建,而只需要从线程池取出。这提升了获取线程的速度。

一、线程池类图

在这里插入图片描述
Executor是一个顶层的线程池接口,ExecutorService接口则在Executor的基础上定义了更多的方法比如:提交任务,执行任务,关闭线程池等。最终的线程池都是ExecutorService的实现类,我们可以用ExecutorService中的方法控制线程池。
在这里必须要介绍的类是Executors类,这是一个工厂类,该类中提供了创建多种JDK自带的线程池的静态方法,我们会在后面详细介绍其中的几种常用线程池。

二、ThreadPoolExecutor类

为什么在这里对ThreadPoolExecutor类进行介绍?我们在实际应用场景中,面对不同的业务需求,需要创建具有不同功能的线程池,例如,定时任务的线程池,固定大小的线程池,弹性大小的线程池等。这时候就需要我们对线程池的参数进行一些设置。Executor的子类中,非抽象类只有ScheduledThreadPoolExecutor类(定时任务),ThreadPoolExecutor类,ForkJoinPool类。我们以ThreadPoolExecutor为例子,介绍一些创建线程池需要设置的参数。

ThreadPoolExecutor的构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这里我们给出参数源码解释

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     */
  1. corePoolSize:核心线程数,表示线程池支持的最小线程数;
  2. maximumPoolSize 最大线程数,当线程数大于核心线程数后,并且阻塞队列里存放满了等待执行的任务,才会创建一个“临时”新线程从阻塞队列头部取出任务,线程池还能接受maximumPoolSize - corePoolSize个“临时”新线程;
  3. keepAliveTime: 保持存活时间,空闲线程的存活时间,为了更好的复用线程;
  4. unit: 线程存活时间的单位;
  5. workQueue: 阻塞队列,等待线程的存放任务的队列;
  6. threadFactory:创建线程的工厂类,通常我们会自定义一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位;
  7. handler:线程池执行拒绝策略,当线数量达到maximumPoolSize大小,并且workQueue也已经塞满了任务的情况下,线程池会调用handler拒绝策略来处理请求。

假设:线程池corePoolSize=5,线程池初始化时不会自动创建线程,当有4个任务同时进来时,执行execute方法会新建【4】条线程来执行任务;
前面的4个任务都没完成,现在又进来2个任务,会新建【1】条线程来执行任务,这时poolSize=corePoolSize,还剩下1个任务,线程池会将剩下这个任务塞进阻塞队列(workQueue)中,等待空闲线程执行;
如果前面6个任务还是没有处理完,这时又同时进来了5个任务,此时还没有空闲线程来执行新来的任务,所以线程池继续将这5个任务塞进阻塞队列,但发现阻塞队列已经满了,核心线程也用完了,还剩下1个任务不知道如何是好,于是线程池只能创建【1】条“临时”线程来执行这个任务了;
这里创建的线程用“临时”来描述还是因为它们不会长期存在于线程池,它们的存活时间为keepAliveTime,并且poolSize<maximumPoolSize。此后线程池会维持最少corePoolSize数量的线程。

三、常见线程池的创建

1. newFixedThreadPool

它是一种固定大小的线程池;corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。

代码如下(示例):

public class ThreadPoolTests {
	//使用日志打印线程信息
	private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
	//普通线程池,线程池内固定拥有5个线程
	ExecutorService executorService = Executors.newFixedThreadPool(5);
	
	private void sleep (long m){
        try {
            Thread.sleep(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
	
    @Test
    public void testExecutorService(){
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Hello ExecutorService");
            }
        };

        for (int i = 0; i < 10; i++){
            executorService.submit(task);
        }

        //sleep(10000);
    }
}

日志信息打印结果:

2021-07-26 16:55:55,535 INFO [pool-1-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService

我们可以观察到线程池pool-1中的1-5个线程交替输出“Hello ExecutorService”。


2. newScheduledThreadPool

代码如下(示例):

public class ThreadPoolTests {
	//使用日志打印线程信息
	private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
	// JDK可定时执行任务的线程池
    private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
	
	private void sleep (long m){
        try {
            Thread.sleep(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
	//执行定时任务的线程池
    @Test
    public void testScheduledExecutor(){
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Hello ExecutorService");
            }
        };
		//param:任务,第一次执行任务的初始延迟,任务周期间隔,初始延迟和周期参数的时间单位
        scheduledExecutorService.scheduleAtFixedRate(task, 10000, 1000, TimeUnit.MILLISECONDS);
        sleep(30000);
    }
}

日志信息打印结果:

2021-07-26 17:01:44,827 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:45,829 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:46,831 INFO [pool-2-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:47,837 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:48,836 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:49,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:50,827 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:51,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:52,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:53,826 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:54,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:55,835 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:56,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:57,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:58,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:59,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:00,836 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:01,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:02,830 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:03,839 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:04,830 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService

3. CachedThreadPool

它是一个可以无限扩大的线程池;它比较适合处理执行时间比较小的任务;corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程
代码如下(示例):

public class ThreadPoolTests {
	//使用日志打印线程信息
	private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
	// JDK可定时执行任务的线程池
    private ExecutorService CacheExecutorService = Executors.newCachedThreadPool();
	
	//执行cache线程池
    @Test
    public void testCacheExecutorService(){
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Hello ExecutorService");
            }
        };
        for (int i = 0; i < 10; i++){
            CacheExecutorService.submit(task);
        }
    }
}

运行结果:

2021-07-26 19:01:51,856 INFO [pool-3-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,856 INFO [pool-3-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-7] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-6] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-8] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,859 INFO [pool-3-thread-9] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,859 INFO [pool-3-thread-10] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService

我们可以看到每提交一个任务,都会有一条新新城产生。

4. SingleThreadExecutor

它只会创建一条工作线程处理任务;采用的阻塞队列为LinkedBlockingQueue;
代码如下(示例):

public class ThreadPoolTests {
	//使用日志打印线程信息
	private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
	// JDK可定时执行任务的线程池
    private ExecutorService SingleExecutorService = Executors.newSingleThreadExecutor();
	
	@Test
    public void testSingleExecutorService(){
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Hello ExecutorService");
            }
        };
        for (int i = 0; i < 10; i++){
            SingleExecutorService.submit(task);
        }
    }
}

运行结果:

2021-07-26 19:07:10,887 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService

每次都是同一条线程在执行任务。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值