一、线程池的定义
线程Thread是一个重量级资源,线程的创建、启动和销毁是比较耗费系统资源的。所以,为了有效的管理线程,JDK1.5之后就引入了线程池的概念。线程池就是一个能够容纳多个线程的容器。这样,当有新的请求到达时,程序就会从线程池拿一个线程去处理,而不是创建新的线程;当请求处理完成后,也不是直接关闭线程,而是将这个线程归还给线程池,后续的请求可以重复使用。
二、线程池的创建
java中,线程池的创建也很简单,只需要调用Executors类中相应的方法即可。常用的创建线程池的方法:
//创建只有一个线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//创建固定长度的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//创建一个可缓存的线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
但很不幸的是,阿里巴巴开发手册中已经不允许我们采用这种方式创建线程池了,而是直接使用ThreadPoolExecutor这个类去创建。
三、ThreadPoolExecutor的7个主要参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
int corePoolSize:核心线程大小,线程池最小的线程数量。
int maximumPoolSize:最大线程大小,线程池的最大的线程数量
long keepAliveTime:超过corePoolSize的线程在不活动的状态下存活的最大时间。
TimeUnit unit:keepAliveTime的时间单位。
BlockingQueue<Runnable> workQueue:任务队列。
ThreadFactory threadFactory:线程池工厂,用来创建线程。
RejectedExecutionHandler handler:拒绝策略。
四、线程池任务执行流程
(1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
(2)当线程池超过corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。
(3)当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。
(4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。
(5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程。
(6)当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将被关闭。
可以用银行来记忆,平时就开2个窗口,3个等待座位,最大窗口5个:
- 当来的人数<=2的时候,来了就直接办理了;
- 当人数>2且人数<=5的时候,那么再来办业务的人只能座位区等待了;
- 当人数>5的时候,这时候银行领导一看忙不过来了,就只能开新的窗口办理业务了。
- 当人数>8的时候,所有窗口都满了,这时候如果再来人办理业务的话,门口的保安则需要进行一定的拦截了。比如:直接放弃不给办理了。
- 当过了一段时间之后,一直没有那么多人再来办理业务的时候,那么新开的3个窗口就会关闭掉,只剩下2个窗口办理业务。
- 当领导发话让这2个窗口也可以关闭的时候,那么长时间之后没有人来办理业务的时候这两个窗口也会关闭。
五、阻塞队列
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列(常用)
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列
SynchronousQueue: 一个不存储元素的阻塞队列(常用)
六、拒绝策略
常见的拒绝策略主要有4种:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 这是默认的一种方式。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
七、如何设置线程池的参数
我们需要合理配置最大线程数maximumPoolSize,这需要看我们的业务是io密集型还是cpu密集型。
cpu密集型
cpu密集的意思是该任务需要大量的运算,而没有阻塞,cpu一直全速运行。
cpu密集型任务配置尽可能少的线程数量:以保证每个cpu高效的运行一个线程。
一般公式:cpu核数+1个线程的线程池。
io密集型
io密集型,即该任务需要大量的io,即大量的阻塞。在单线程上运行io密集型的任务会导致浪费大量的cpu运算能力浪费在等待。所以在io密集型任务中使用多线程可以大大的加速程序运行,这种加速主要就是利用了被浪费掉的阻塞时间。
一般公式:cpu核数 * 2个线程的线程池。
查看CPU核数:
System.out.println(Runtime.getRuntime().availableProcessors());