线程池(一)

线程池

创建线程池

  • 通过 ThreadPoolExecutor 手动创建线程池。new ThreadPoolExecutor(…); 推荐!!!
  • 通过 Executors 执行器自动创建线程池。Executors.new… ; eg.Executors.newFixedThreadPool();

以上两大类创建线程池的方式,共有 7 种具体实现方法,这 7 种实现方法分别是:

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

  2. Executors.newCachedThreadPool:创建一个可缓存的无核心线程的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。用于短时间内有突发大量任务的处理场景

  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。仅一个核心线程。可用于数据库操作,文件操作。

  4. Executors.newScheduledThreadPool:创建固定长度线程池,一个可以执行延迟or定时任务的线程池。核心线程数固定,非核心线程数无限,任务队列延迟阻塞。

  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。此线程池可以看作是 ScheduledThreadPool 的单线程池版本。

  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。

  7. ThreadPoolExecutor:最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。

为什么推荐使用ThreadPoolExecutor

ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控

ThreadPoolExecutor 的参数

  1. corePoolSize核心线程数量(必须)、maximumPoolSize最大线程数量(必须)、keepAliveTime 、TimeUnit、queue、线程工厂、拒绝策略。

  2. 核心线程数量:线程池会维护最小线程数量,一般就算这些线程空闲也不会销毁,除非设置了allowCoreThreadTime

  3. 最大线程数量:线程最大数量,减去核心就是救济线程数

  4. 空闲线程存活时间:当有空闲线程时,而且当前线程数量超过核心线程数量,那么当前线程在一定时间后就会销毁;

  5. 空闲线程存活时间单位:指定 keepAliveTime 参数的时间单位

  6. 工作队列:新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。

  7. 线程工厂:创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

  8. 拒绝策略:当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,拒绝策略,就是解决这个问题的

4种工作队列

  • ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

  • LinkedBlockingQuene:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

  • SynchronousQuene:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

  • PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

4种拒绝策略

  • CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。

  • AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

  • DiscardPolicy:该策略下,直接丢弃任务,什么都不做。

  • DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

Executors 返回的线程池对象的弊端如下

  1. FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  2. CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

相比于单个线程数的线程池,为什么不直接创建一个线程?

因为线程池:

  • 可以复用线程:即使是单个线程池,也可以复用线程。
  • 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。

任务提交到线程池,执行过程

当任务被提交到线程池,首先判断是否有空闲线程,如果有则直接交由处理,如果没有则放入队列中,如果队列也满了,直接放给救济线程,救济线程只有在此时是直接拿刚来的任务,后面救济线程只从队列中取;后面的任务来了,先看核心线程,再看队列,若都满了,拒绝策略。

submit()和execute()方法区别

  • submit()方法可以提交Callable和Runnable类型的任务,并且可以返回任务执行的结果。如果任务执行成功,submit()方法会返回一个Future对象,可以通过该对象获取任务执行的结果。如果任务执行失败,submit()方法会抛出异常。

  • execute()方法只能提交Runnable类型的任务,不能返回任务执行的结果。如果任务执行失败,execute()方法会抛出异常。

如果需要获取任务执行的结果,可以使用submit()方法;

如果不需要获取任务执行的结果,可以使用execute()方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值