不读书的人,思想就会停止 !
0. 思维导图
线程池是很多编程语言中都有的,虽然我现在很少写java了,但是对于面试或其他语言,其实都是相通的。
这篇是聊下线程的基本内容,下面是基本的思维导图。
线程池
1. 为什么需要线程池
任何程序,都是一些线程,执行一些任务组成的。任务会很多,如果我们每个任务都创建一个线程去执行。那么随着任务的增加,创建的线程数激增,会有很多问题:
- 系统可以创建的线程数不是无限的,因为线程需要占用内存等资源信息。
- 不是线程越多越好,线程越多,cpu的核数是固定的,那么多个线程切换的时候,就会发生上下文切换影响性能。
- 线程创建和销毁本身也有开销,可能线程创建和销毁的时间比执行任务的时间还长,得不偿失。
public class OneTask {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.start();
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println("Thread Name: " + Thread.currentThread().getName());
}
}
}
如果只有一个线程执行任务那,又存在着cpu利用不充分的问题,比如我们很多任务可能需要IO,在等待IO的时候,线程sleep会让出cpu,从而不利于程序整体性能提升。
一个任务一个线程,系统执行慢;每个任务一个线程,又导致线程过多,导致一系列上述问题。
2. 线程池
为了让系统执行的足够快,又不想创建过多线程,占用过多资源,线程池就应运而生。
线程池有以下好处:
- 线程池,故名思意,池子里面创建了一定的线程数,任务来了之后,直接运行,提升响应速度。
- 线程池有不少线程是长期存在的,那就不需要过多的线程创建和销毁的开销。
- 线程池统筹cpu和内存的使用,线程不够的时候创建,线程空闲下来的时候可以进行销毁,在系统性能和资源占用中保持一个平衡。
3. Java线程池参数说明
这些也是面试中常问的问题,Java中线程池的参数说明如下:
- corePoolSize 核心线程数
- maxPoolSize 最大线程数
- keepAliveTime+ 时间单位 空闲线程的存活时间
- ThreadFactory 线程工厂,用来创建线程。
- workQueue 用来存放任务的队列
-
Handler 处理拒绝任务接口实现
举例:
线程池
3.1 线程池中线程数
Java中的线程数开始的时候是0 ,当添加任务的时候,首先查看线程池中线程数是否达到了核心线程数(corePoolSize),没有则创建; 如果达到了核心线程数,则看下队列是否满了,如果队列也满了,则判断限制线程数是否达到了最大线程数(maxPoolSize),如果没达到,则继续创建线程;如果达到了,则按照Handler处理拒绝任务的方法来处理。
线程池从这个图需要注意的:
- 线程池初始的状态下是没有线程的,直到来了一个任务。
- 非核心线程数的创建是在队列满了之后再创建,如果使用无界队列,比如
LinkedBlockingQueue
则队列永远不会满,也就不会创建非核心线程,线程池的最大线程数只能为corePoolSize 。
3.2 keepAliveTime
keepAliveTime和时间单位决定了非核心线程的存活时间,如果线程池的线程超过核心线程数,会根据这个时间来销毁非核心线程数,从而减少空闲时候的资源占用;任务多的话,又会根据上图规则来继续创建非核心线程。具备线程数弹性变化的能力。
3.3 ThreadFactory
创建线程的线程工厂,我们如果需要定制线程,比如设置线程的名称等,可以自定义线程工厂来替换默认的线程工厂来创建线程。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
3.4 workQueue
Java线程池中的任务队列,常用的几种:
LinkedBlockingQueue
没有容量限制的队列,也真是因为无限,则有可能造成OOM问题。
SynchronousQueue
这个队列很奇怪,没有空间存放数据,来了任务直接交给线程,不进行存放,性能好。也许会奇怪,为啥要又这样队列,我觉得解耦是一方面,解耦生产者和消费者,比如solr的客户端我记得再6.3版本的时候就是用这种队列来提交任务的。
DelayedWorkQueue
延迟任务队列,当我们做任务定时调度的时候需要,使用此队列。采用堆这种数据结构,可以保持先执行的任务放在队头。
这个队列也是无界队列,也容易引起OOM问题。
四 线程拒绝任务
线程在两种情况下会拒绝任务:
- 线程池已经调用shutdown了,相当于已经关闭了线程池,当然再来的任务会被拒绝掉。
- 按照刚才线程池中线程的创建过程描述,当线程已经无法再创建非核心线程的时候,再来的任务就会被拒绝掉。
拒绝又分几种拒绝策略:
-
AbortPolicy
: 拒绝抛异常RejectedExecutionException
这是线程池默认的拒绝策略,如果需要拒绝,则抛出运行时异常:RejectedExecutionException
调用者自行决定如何处理。 -
DiscardPolicy
: 这个比较狠,直接默默地抛弃,不通知,容易丢失任务。 -
DiscardOldestPolicy
: 这个是删除在队列中存活最久的任务,腾出空间给新来的任务,也是默默地抛弃,无提示。 -
CallerRunsPolicy
: 这个是我觉得比较好的策略,它不拒绝,如果满足拒绝条件后,哪个线程执行提交任务,就在哪个线程执行这个任务。你不是提交快慢,那我就交给你执行,这样可以防止提交太快导致无法执行的问题,线程执行了任务就会减慢了提交任务的速度,给线程池执行腾出了时间。
网友评论