Java四种线程池原理&应用分析

线程池种类

线程池的实现分为四类:

Executors.newCachedThreadPool()
Executors.newFixedThreadPool(int size)
Executors.newScheduledThreadPool(int size)
Executors.newSingleThreadExecutor()
ThreadPoolExecutor构造参数

四个线程池类都是通过Executors工厂来实现的,通过内部代码不难发现它们内部构造都依赖于ThreadPoolExecutor实现的,只是不同类型的线程池通过不同的构造参数构建了不同的ThreadPoolExecutor来实现,所以我们这里先来了解一下构造线程池时对应的核心构造参数。
参数最全的构造函数为:

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

对应参数作用如下:

  • int corePoolSize:线程池中维护的核心线程数数量,这里维护的线程是即便线程空闲状态也会保持持有不释放的。
  • int maximumPoolSize:线程池最多能够持有的线程数,如果任务过多,线程数会超过corePoolSize,此时会继续创建新的线程来处理任务,但是最终线程数不能超过maximumPoolSize
  • long keepAliveTime:当线程数超过corePoolSize时,超出部分线程在任务执行完毕后不会马上释放,而是会进入等待,但是不会一直等待,当等待时间超过keepAliveTime后会回收对应线程。
  • TimeUnit unit: 这个很简单,就是keepAliveTime的单位
  • BlockingQueue<Runnable> workQueue:这是一个队列,当外部调用线程池的execute方法请求线程池执行对应任务的时候,可能不一定能够马上获取的执行所需的线程,那么此时会先将这些提交过来的任务放到这个workQueue中,当后续有线程资源空出来后再从队列中取出等待的任务执行。
  • ThreadFactory threadFactory: 这个是一个线程创建的工厂类,通过它对外开放线程创建的逻辑,因为可能某些任务需要创建一些封装的特殊线程,此时可以通过自定义一个线程创建工厂,传入线程池创建构造函数中。
  • RejectedExecutionHandler handler: 这个是设置拒绝策略的,因为有可能我们传入的队列设置了队列的最大长度或者是提交任务的时候还没来得及启动线程池已经要改变状态为SHUTDOWN/STOP,因为以上原因导致任务提交失败,此时就会调用我们设置好的拒绝策略来进行处理。

了解了这些构造参数作用后,我们再来看四种线程池的构造方式和分别对应的应用场景。

newCachedThreadPool()

内部构造函数如下:

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>())

可以看到构造函数中核心线程数为0,线程最大数为Integer.MAX_VALUE,线程回收阀值为60s,采用的是SynchronousQueue这个同步队列。那么这种类型的意思就是说,默认没任务的时候是不会有核心线程的,只有有任务进来之后才会创建线程执行任务,然后任务执行完毕之后60s内如果有其它任务提交,则直接用还没回收的空闲线程来执行,这种模式比较适合的场景是:很多短期异步任务的任务需要提交到线程池执行的情况。

newFixedThreadPool()

构造函数如下:

new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());

可以看到其中corePoolSizemaximumPoolSize大小一样,并且keepAliveTime=0意思就是不过期,说明构造的是一个定长的线程池,线程创建后会一直存在,这样的好处是可以很好的控制资源使用,适用于负载比较高的场景。

newScheduledThreadPool()

构造方法如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }

首先,这种线程池中的线程也是创建后一直存在的,比较特殊的是它的队列是一个DelayedWorkQueue队列,这个队列的作用是可以对任务进行延迟执行任务。

newSingleThreadExecutor()

依据名称也可以很好理解这个了,构造方法如下:

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

可以看到这是一个长度为1的固定线程池,缓存任务的队列是LinkedBlockingQueue,意味着可以无限制的追加等待的任务,由于线程池内部只有一个线程,所以可以理解为任务执行是依据提交顺序顺序执行的,可以保证任务执行顺序,所以对于后台需要单个线程执行并且保证提交任务顺序性的场景,比较适合使用这种线程池。

线程池配置

某些情况下我们可能需要自己直接调用ThreadPoolExecutor来自己定义对应的各项参数,或者是在用Executors工厂创建线程池时要我们自己来指定线程数,这里关于线程数的设置是对线程池性能有很大影响的,所以我们如何设置线程数就十分重要了。设置线程数量需要考虑我们要计划要提交到线程池中的任务的类型来考虑,主要为:

  • CPU密集型任务:指的是任务要执行很多计算类型的操作,十分消耗CPU,例如视频解码、复杂运算等,此类任务如果线程数过多会导致频繁的CPU任务上下文切换,所以此时线程池线程数最好和CPU核心数相近,例如直接设置为CPU核心数个数的线程数。
  • I/O密集型任务:I/O密集型任务就很好理解了,就是任务可能会频繁的需要进行网络、磁盘等IO,此类任务在等待IO的时候CPU消耗很少,这类任务就可以多设置一些线程数,例如可以设置为CPU核心数的两倍的线程数。

参考:https://www.jianshu.com/p/1f5195dcc75b

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值