学习笔记-线程池

一、线程池概念

线程的使用带来了很多好处,比如异步调用,提升性能等,然而,频繁的创建和销毁线程会耗费计算机资源。

类似于数据库连接池,这类带‘池’的功能很好地解决了上述问题。

线程池的核心是创建n个线程放在容器中,等到有任务处理时会将其分配给其中某个线程,执行完之后不会立即销毁,而是放回容器中,等待下个任务执行。

综上,线程池具备如下优点:

1.避免了重复创建和销毁线程,降低了系统资源消耗

2.执行任务时无需重新创建,而是直接从池中获取线程,缩短了执行时间

3.使得线程资源控制成为可能

 

二、线程池创建

 1 public class ThreadPoolTest {
 2 
 3     public void print(){
 4         System.out.println("当前线程" + Thread.currentThread().getName());
 5     }
 6 
 7     public static void main(String[] args) {
 8         ExecutorService service = Executors.newFixedThreadPool(3);
 9         for (int i = 0; i < 1000; i++) {
10             service.execute(new Thread(()-> new ThreadPoolTest().print()));
11         }service.shutdown();
12     }
13 }

这里用的是jdk中提供的api——Executors.newFixedThreadPool(3)创建线程池,该方法会创建一个带有3个线程大小的线程池。

java中提供的以下创建线程池的API

线程池 功能
newFixedThreadPool 创建一个固定大小的线程池
newSingleThreadExecutor 创建一个只有一个线程的线程池
newCachedThreadPool 创建一个根据实际情况调整线程数的线程池
newScheduledThreadPool 创建带有延迟和周期性执行功能的线程池 

 阅读源码不难发现这些api实际上都调了ThreadPoolExecutor类的构造函数,区别在于参数各不相同。

这里选择其中最简单的构造方法相关参数(有些参数是可以通过构造方法传入的,如threadFactory)说明,以此来了解其中差异。

线程池

corePoolSize

(核心线程数)

maximumPoolSize

(最大线程数)

keepAliveTime

(存活时间)

unit

(单位)

workQueue

(保存任务的队列)

threadFactory

(创建线程使用的工厂)

handler

(拒绝策略)

newFixedThreadPool n n 0 毫秒 LinkedBlockingQueue DefaultThreadFactory(默认,可通过构造方法传入) AbortPolicy(默认)
newSingleThreadExecutor 1 1 0 毫秒 LinkedBlockingQueue DefaultThreadFactory(默认,可通过构造方法传入) AbortPolicy
newCachedThreadPool 0 Integer.MAX_VALUE 60 SynchronousQueue DefaultThreadFactory(默认,可通过构造方法传入) AbortPolicy
newScheduledThreadPool n Integer.MAX_VALUE 0 纳秒 DelayedWorkQueue DefaultThreadFactory(默认,可通过构造方法传入) AbortPolicy

1.newFixedThreadPool:核心线程数和最大线程数相同,为用户指定的值,不存在工作线程,因此无论有多少任务过来,线程池中线程最大只能是n,不会耗费多余资源创建线程,适用于负载比较大的场景下对线程资源的管控;

2.newSingleThreadExecutor:核心线程数和最大线程数为1,由此创建的是单线程的线程池。

3.newCachedThreadPool:无核心线程,最大线程为integer的最大值,线程空闲60秒之后便会销毁。这是一种伸缩性很强的线程池,当大量任务执行时会不断创建线或使用空闲线程来执行,当任务执行完之后又能全部销毁。

4.newScheduledThreadPool:继承了ThreadPoolExecutor,并另外提供一些调度方法以支持定时和周期任务。

三、线程池执行流程

4、线程池使用

线程池的创建因业务,机器配置等不同而不同,线程池的创建应该符合当前业务场景,使用ThreadPoolExecutor灵活度更高,不建议使用Executors创建线程池。

4.1 配置线程池大小

首先分析线程池中任务的特性,是CPU密集型还是IO密集型。

CPU密集型:线程数=CPU核心数

IO密集型:一般可配置核心数的两倍,线程池设定最佳线程数目=((线程池设定的线程等待时间+线程CPU时间)/线程CPU时间)*CPU数目

 4.2 execute和submit的区别

  1. execute只可以接收一个Runnable参数,submit可接收Runnable和Callable两种参数
  2. execute会抛出异常,submit不会,除非调用Future.get();
  3. execute没有返回值,submit有返回值

 4.3 拒绝策略

  1. AbortPolicy : 直接抛出异常,这是默认策略
  2. CallerRunsPolicy : 用调用者所在线程来执行任务
  3. DiscardOldestPolicy : 丢弃阻塞队列中最靠前的任务,并执行当前任务
  4. DiscardPolicy : 直接丢弃任务

4.4 队列的选择

  1. LinkedBlockingQueue是一个无界队列,因此不会触发拒绝策略,适合存放重要,不能丢弃的任务,另外,无界也可以应对高并发场景
  2. ArrayBlockingQueue是有界队列,当线程池中线程数达到corePoolSize,且任务队列已满,同时线程数未超过maxPoolSize时会创建工作线程执行。因此通过ArrayBlockingQueue和maxPoolSize配置使用可以控制线程资源,适合重要性低的任务场景
  3. SynchronousQuene只存放一个任务的队列,当队列满且有新任务时,若线程数未超过maxPoolSize会创建新线程执行。因此可以通过此队列可以为每个新来的任务分配一个线程,与maxPoolSize配合使用可以控制线程数量,适合处理高并发且实时性要求高的场景,但可能会耗尽系统资源
  4. PriorityBlockingQueue具有优先级的无界阻塞队列
posted @ 2020-09-30 18:27  落雨有清·风  阅读(113)  评论(0编辑  收藏  举报