Java线程池知识点总结

目录

1.多线程负责解决什么问题

2.线程池负责解决什么问题

3.自定义线程池七个常用参数

4.JDK提供的默认线程池实现

5.线程池执行流程

6.自定义线程池参数计算

7.线程池创建的时候内部有多少可用的线程?什么时候才真正有活跃的线程?

9.线程池中的线程出现异常,线程池会怎么处理这个线程


1.多线程负责解决什么问题

       多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。同时也可以快速响应前端,将耗时任务交给线程去执行,提高前端用户的交互体验。

2.线程池负责解决什么问题

       线程池是存放有一组线程的一个容器。线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。合理的使用线程池可以降低资源消耗,提高响应速度,提高线程的可管理性。

3.自定义线程池七个常用参数

  • corePoolSize:线程池的核心池大小,换句更精炼的话:corePoolSize表示允许线程池中允许同时运行的最大线程数。

  • maximumPoolSize:线程池允许的最大线程数,他表示最大能创建多少个线程maximumPoolSize肯定是大于等于corePoolSize。

  • keepAliveTime:表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize 时,keepAliveTime才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间keepAliveTime,那么才会shutdown。

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

  • workQueue:阻塞队列。新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

    • ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。

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

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

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

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

  • handler:当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制执行拒绝策略

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

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

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

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

4.JDK提供的默认线程池实现

JDK1.8共提供了六种线程池默认实现,分别是:

    (1)newCachedThreadPoo

    (2)newFixedThreadPool

    (3)newSingleThreadExecutor

    (4)newScheduledThreadPool

    (5)newSingleThreadScheduledExecutor

    (6)newWorkStealingPool

newCachedThreadPool

       配置:核心线程数为0,线程总数为Integer最大值,非核心线程过期时间60秒,阻塞队列类型为同步阻塞队列。

       作用:缓存线程池,可灵活回收空闲线程,若无可回收线程则新建线程处理任务,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

newFixedThreadPool

       配置:核心线程数与线程总数相同,由构造参数传递,阻塞队列类型为链式阻塞队列。

       作用:定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newSingleThreadExecutor

       配置:FixedThreadPool的特殊情况,核心线程数与线程总数相同且为1,阻塞队列类型为链式阻塞队列。

       作用:单线程化的线程池,使用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newScheduledThreadPool

       super调用的还是ThreadPoolExecutor的构造方法。

       配置:核心线程数参数传入,线程总数为Integer最大值,阻塞队列类型为延时阻塞队列。

       作用:定长线程池,支持定时及周期性任务执行。

newSingleThreadScheduledExecutor

       配置:核心线程数为1,线程总数为Integer最大值,阻塞队列类型为延时阻塞队列。

       作用:单线程化的线程池,支持定时及周期性任务执行。

newWorkStealingPool:(源码还没看过,先记录在这里。。。)

       作用:JDK1.8新加入的一种线程池,它底层实现使用的ForkJoinPool 类,该线程池不会保证任务的顺序执行,构造方法参数中为线程并发的数量。

5.线程池执行流程

图片借鉴自:https://www.jianshu.com/p/eed5ff8d143e?utm_campaign=hugo

  • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

  • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

  • 如果队列已经满了,则在总线程数不大于maximumPoolSize的前提下,则创建新的线程

  • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

  • 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

6.自定义线程池参数计算

(1)先看下机器的CPU核心数,然后在设定具体参数:

System.out.println(Runtime.getRuntime().availableProcessors());

(2)分析下线程池处理的程序是CPU密集型,还是IO密集型

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

IO密集型:核心线程数 = CPU核数 * 2

CPU密集型:

       CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。

       CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间

IO密集型:

       IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。

7.线程池创建的时候内部有多少可用的线程?什么时候才真正有活跃的线程?

       从注释可以看到这个HashSet是存放所有的工作线程的容器,也就是线程池最核心的容器。我们看到ThreadPoolExecutor的构造函数中并没有对workers进行添加操作。只是对于变量进行了一个赋值操作,也就是说在ThreadPoolExecutor被new出来后workers容器里面是空的,所以线程池创建时内部可用线程数为0。那什么时候才创建了线程放在线程池中?我们知道提交任务无非两种方式execute和submit。

       可以看到submit提交的任务最终都是走到了execute方法中。

三个if的判断逻辑:

  • 如果运行的线程少于corePoolSize,尝试以给定的命令作为第一个启动新线程

  • 如果一个任务可以成功地排队,那么我们仍然需要再次检查是否应该添加线程,还是应该在进入此方法后关闭池。

  • 如果不能对任务进行排队,则尝试添加一个新线程。

Debug自测:

       可以看到在线程池的构造方法执行结束后真正存放线程的set为0。

       当代码执行到submit后进去到execute方法,才往容器中存放了一个工作线程。

9.线程池中的线程出现异常,线程池会怎么处理这个线程

       线程池针对不同的提交方式会抛出堆栈异常,execute方法会抛出异常而submit则不会。出现异常不会影响其他线程任务的执行,最后该异常线程会被清理,线程池会重新添加一个新线程作为补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值