java线程池

java线程池

Posted by John Doe on 2021-05-30
Words 1.5k and Reading Time 5 Minutes
Viewed Times

Java线程池

线程池的概念

线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

使用线程池的原因

  • 降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。

  • 提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。

  • 提⾼线程的可管理性。线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。

线程池7大参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}

corePoolSize:核心线程数,核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。

maximumPoolSize:最大线程数, 当workQueue缓冲队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数 量变为最⼤线程数。

keepAliveTime:非核心线程存活时间,当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任务提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁。

unit: 时间单位,keepAliveTime 参数的时间单位。

workQueue:缓冲队列,当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达 到的话,新任务就会被存放在队列中。

threadFactory:线程工厂,executor 创建新线程的时候会⽤到。

handler:拒绝策略,当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

几种缓冲队列

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

  • ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

  • LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

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

几种拒绝策略

  当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
  • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

jdk自带的三种线程池源码分析

newCachedThreadPool

1
2
3
4
5
6
7
8
9
// 实例化
ExecutorService newCachedExecutorService = Executors.newCachedThreadPool();

//源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

corePoolSize核心线程数为0,创建的都是非核心线程,有多少任务创建多少个线程(最多创建Integer的最大值以内的非核心线程数2^31 - 1,任务执行较快的话线程复用除外,有CPU百分百占用风险)

synchronousQueue使用同步队列直接新建线程执行任务。

newFixedThreadPool

1
2
3
4
5
6
7
8
9
// 实例化
ExecutorService newFixedExecutorService = Executors.newFixedThreadPool(10);

//源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

corePoolSize核心线程数为实例化时传入参数,创建的都是核心线程,没有非核心线程。使用LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;有OOM风险

newSingleThreadExecutor

1
2
3
4
5
6
7
8
9
10
// 实例化
ExecutorService newSingleExecutorService = Executors.newSingleThreadExecutor();

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

corePoolSize核心线程数为1,只有一个核心线程。使用LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;有OOM风险

阿里编程手册上推荐的创建线程池的方式

注:由于Executors创建的线程池还多参数的设置都是什么Integer.MaxValue和无上限的BlockQuene等由此可能会导致CPU百分百占用(创建了无数个非核心线程)和OOM(缓冲队列溢出放不下)问题, 如果我们不加思考的使用,很容易把线程池用在错误的场景上, 因此阿里巴巴的编程手册上不推荐Executors创建线程池的方式, 它要求我们必须自己设置参数来创建线程池。

类似于上文中jdk自带的线程池实现,阿里编程手册推荐我们自己根据业务与实际自己调解参数调用ThreadPoolExecutor的构造方法实现线程池。

1
2
3
4
// 创建数组型缓冲等待队列
BlockingQueue<Runnable> blockQueue = new ArrayBlockingQueue<Runnable>(10);
// ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, blockQueue);

如何合理配置线程池的大小

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

如果是IO密集型任务,参考值可以设置为2*NCPU


This is copyright.

...

...

00:00
00:00