Java线程池详解

1 概述

在我们工作中,通常需要创建一些线程来处理一些业务,最常用的就是记录系统日志,这个时候如果我们手动创建线程,比如采用最传统的方式,那么这个时候就会出现反复创建很多线程,然后当线程使用完的时候,这些线程又会被关闭,这样就会浪费很多时间来反复创建和销毁线程。那么这个时候就需要我们现在要谈的线程池上场了。

使用线程池,我们可以预先创建多条线程,在使用的时候不再需要我们去创建,而是直接从线程池中取出线程来使用就可以了,当线程使用完了,也不需要关闭线程,而是将线程归还给线程池,这样就可以减少频繁地创建和关闭线程所带来的cpu的消耗。

2 线程池设计结构

这里我们来看一看Java中线程池是如何设计的,首先来看一看下面的类图。

图片来源于网络

从上图我们可以对Java线程池的设计有一个大概的认识,其实在我们使用线程池的时候,用得最多的就是ThreadPoolExecutor这个类。

接下来我们来看看JDK提供的几种常用的Java线程池。

3 常用的Java线程池

通过查看Executors工具类的源码,这个工具类提供了以下四个函数来分别生成四种线程池。

(1)newSingleThreadExecutor:线程池种仅仅拥有一个线程。如果有多余的任务提交到线程池中,任务将会被添加到阻塞队列中,当有线程池中的线程空闲下来的时候再去队列中获取任务执行。

(2)newFixedThreadPool:创建固定数量线程的线程池。如果有多余的任务提交到线程池中,任务将回被添加到阻塞队列中,当有线程池中的线程空闲下来的时候再去队列中获取任务执行。

(3)newCachedThreadPool:缓存线程的线程池。这个线程池初始状态不会创建任何线程,然后线程一旦创建过后,将会在使用后保存线程60秒的存活时间,然后再60秒内,如果没有任务使用线程,线程将被关闭。

(4)newScheduledThreadPool:定时线程池。该线程可用于周期性地执行任务。

针对上面的四种线程池的生成函数,我们来看一下newSingleThreadExecutor的源码是怎样实现的。

public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

通过查看源码我们可以看出newSingleThreadExecutor的实现其实就依赖于ThreadPoolExecutor的构造函数。

4 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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

针对上面构造函数的参数,这里进行详细说明。

(1)corePoolSize:线程池的核心池大小。当线程池被创建的时候,线程池中没有任何线程,在任务到来的时候开始创建线程,知道线程池中的线程数量达到corePoolSize的时候,如果这个时候还有任务到来,任务就直接被放进阻塞队列当中,当线程池中的线程空闲出来的时候再从阻塞队列中去获取任务进行执行。

(2)workQueue:阻塞队列。阻塞队列用于当线程池的线程全部都在工作状态的时候,这个时候还有任务往线程池中提交,就直接存放进入阻塞队列。当然阻塞队列也有不同的种类,下面我们分别说明。

有界队列ArrayBlockingQueue:基于数组的先进先出队列,这个队列的创建必须指定大小。

无界队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建这个队列的时候没有指定大小,则队列的大小为Integer.MAX_VALUE。

直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

(3)maximumPoolSize:最大线程数。当阻塞队列已经被放满的时候,还继续向线程池提交任务,这个时候将会继续创建线程来执行提交的任务,直到线程数量达到maximumPoolSize的时候。

(4)keepAliveTime和unit:超出corePoolSize的线程存活时间和时间的单位。

(5)threadFactory:创建线程的工厂。

(6)handler:拒绝策略。当线程池中的线程数量达到maximumPoolSize的时候,如果还有任务被提交过来,这个时候就会执行这个参数的rejectedExecutor函数。具体的据觉策略的种类我们可以查看ThreadPoolExecutor的源码。

CallerRunsPolicy,只要线程池没有关闭,该策略直接在调用者线程中运行当前丢弃的任务,其实这相当于任务没有被丢弃,这样将会造成任务提交线程的性能急剧下降。

AbortPolicy,该策略直接抛出异常来终止任务的执行。

DiscardPolicy,丢弃任务,不做任何处理。

DiscardOldestPolicy,剔除阻塞队列中最老的任务,然后再次向线程池提交任务。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值