1. 9IM首页
  2. 热点

谈谈对线程池的理解

首先线程池有什么作用?

  • 1.提高响应速度,如果线程池有空闲线程的话,可以直接复用这个线程执行任务,而不用去创建。
  • 2.减少资源占用,每次都创建线程都需要申请资源,而使用线程池可以复用已创建的线程。
  • 3.可以控制并发数,可以通过设置线程池的最大线程数量来控制最大并发数,如果每次都是创建新线程,来了大量的请求,可能会因为创建的线程过多,造成内存溢出。
  • 4.更加方便来管理线程资源。

线程池有哪些参数?

corePoolSize 核心线程数

该线程池中核心线程数最大值,添加任务时,即便有空闲线程,只要当前线程池线程数<corePoolSize,都是会新建线程来执行这个任务。并且核心线程空闲时间超过keepAliveTime也是不会被回收的。(从阻塞队列取任务时,如果阻塞队列为空,核心线程的会一直卡在workQueue.take方法,被阻塞并挂起,不会占用CPU资源。非核心线程会workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) ,如果超时还没有拿到,下一次循环判断compareAndDecrementWorkerCount就会返回null,Worker对象的run()方法循环体的判断为null,任务结束,然后线程被系统回收)

maximumPoolSize 最大线程数

该线程池中线程总数最大值 ,一般是用于当线程池线程都在执行任务,并且等待队列满了时,如果当前线程数<maximumPoolSize,可以创建一个新线程来执行任务。maximumPoolSize一般也可以用来现在最大线程并发执行量。

workQueue 等待队列

等待队列,一般是抽象类BlockingQueue的子类。

ArrayBlockingQueue有界队列,一种使用数组实现的先进先出的有界阻塞队列。支持公平锁和非公平锁,底层数据结构是数组,需要指定队列的大小。
LinkedBlockingQueue无界队列,一种使用链表实现的先进先出的有界阻塞队列。默认的容量是Interge.MAX_VALUE,相比于ArrayBlockingQueue具有更高的吞吐量,但是却丢失了随机存储的特性。默认大小是Integer.MAX_VALUE,也可以指定大小。newFixedThreadPool()和newSingleThreadExecutor()的等待队列都是这个阻塞队列。
LinkedBlockingDeque一种使用链表实现的具有双向存取功能的有界阻塞队列。在高并发下,相比于LinkedBlockingQueue可以将锁竞争降低最多一半
PriorityBlockingQueue一种提供了优先级排序的无界阻塞队列。如果没有提供具体的排列方法,那么将会使用自然排序进行排序,会抛出OOM异常。
SynchronousQueue一种不存储任务的同步队列。每一次的插入操作都必须等待其他线程进行相应的删除操作。支持公平锁和非公平锁。同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。newCachedThreadPool()的等待队列都是SynchronousQueue,所以一般都是对于每一个任务都是创建一个新线程去执行。
LinkedTransferQueue一种使用链表实现的无界阻塞队列。
DelayQueue一种无界的延时队列,可以设置每个元素需要等待多久才能从队列中取出。 延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。一般是用于定时任务线程池newScheduledThreadPool。

keepAliveTime非核心线程闲置超时时长

非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true),则会也作用于核心线程。

RejectedExecutionHandler 拒绝处理策略

拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种拒绝处理的策略为 :

  1. ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃等待队列头部(最旧的)的任务,然后重新尝试执行程序,将任务添加到队列中(如果再次失败,重复此过程)。
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

Executors提供的四种线程池的使用场景。

Executors提供四种线程池,分别为:

newFixedThreadPool 定长线程池

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待(比较适合需要控制并发量的情况)。主要是通过将核心线程数设置为与最大线程数相等实现的。缺点是LinkedBlockingQueue队列的默认长度是Integer.MAX_VALUE,也存在内存溢出的风险。

与CachedThreadPool的区别

  • 因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核心线程。 而CachedThreadPool因为corePoolSize=0,所以只会创建非核心线程。
  • 在 getTask() 方法,如果队列里没有任务可取,线程会一直阻塞在 LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在60s后收回。
  • 由于线程不会被回收,会一直卡在阻塞,所以没有任务的情况下, FixedThreadPool占用资源更多
  • 都几乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列可以很大(最大为Integer最大值),故几乎不会触发拒绝策略;CachedThreadPool是因为线程池很大(最大为Integer最大值),几乎不会导致线程数量大于最大线程数,故几乎不会触发拒绝策略。
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

newSingleThreadExecutor 单线程池

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。主要是通过将核心线程数和最大线程数都设置为1来实现。

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

newCachedThreadPool可缓存线程池

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。但是由于最大线程数设置的是Integer.MAX_VALUE,存在内存溢出的风险。

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

CacheThreadPool运行流程如下:

  1. 提交任务进线程池。
  2. 因为corePoolSize为0的关系,不创建核心线程,线程池最大为Integer.MAX_VALUE。
  3. 尝试将任务添加到SynchronousQueue队列。
  4. 如果SynchronousQueue入列成功,等待被当前运行的线程空闲后拉取执行。如果当前没有空闲线程,那么就创建一个非核心线程,然后从SynchronousQueue拉取任务并在当前线程执行。
  5. 如果SynchronousQueue已有任务在等待,入列操作将会阻塞。

当需要执行很多短时间的任务时,CacheThreadPool的线程复用率比较高, 会显著的提高性能。而且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool并不会占用很多资源。

newScheduledThreadPool定时执行线程池

创建一个定时执行的线程池,主要是通过DelayedWorkQueue来实现(该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素)。支持定时及周期性任务执行。但是由于最大线程数设置的是Integer.MAX_VALUE,存在内存溢出的风险。

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}

原创文章,作者:9IM,如若转载,请注明出处:https://www.9im.cn/741.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论列表(2条)

  • 崩天的勾玉
    崩天的勾玉 2020年9月19日 上午9:01

    感觉这主题不适合阅读,读起来有点费劲