文章目录
一、线程池如何使用
1、架构说明
线程池的底层结构就是ThreadPoolExecutor
2、编码实现
(1)Executors.newScheduledThreadPool():定时执行线程(了解)
(2)Executors.newWorkStealingPool(int):java8新增,可以使用处理器作为并行级别(了解)
(3)Executors.newFixedThreadPool(int):执行长期的任务,性能好很多(重点)
(4)Executors.newSingleThreadExecutor():一个任务一个任务执行的场景(重点)
(5)Executors.newCachedThreadPool():执行很多短期异步的小程序或负载较轻的服务器(重点)
简单的使用:
package com.interview.javabasic.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by luyangsiyi on 2020/3/14
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
//一池5个处理线程
//ExecutorService threadpool = Executors.newFixedThreadPool(5);
//一池1个处理线程
//ExecutorService threadpool = Executors.newSingleThreadExecutor();
//一池n个处理线程
ExecutorService threadpool = Executors.newCachedThreadPool();
//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
try{
for(int i = 1; i <= 10; i++){
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t办理业务");
});
}
} catch (Exception e){
e.printStackTrace();
} finally {
threadpool.shutdown();
}
}
}
3、ThreadPoolExecutor
重点线程池的底层源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
继续深入到ThreadPoolExecutor的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
二、线程池的底层工作原理
1、线程池的重要参数
(1)corePoolSize
线程池中的常驻核心线程数。
(2)maximumPoolSize
线程池能够容纳同时执行的最大线程数,此值必须大于等于1。
(3)keepAliveTime
多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲的线程会被销毁直到剩下corePoolSize个线程为止。
(4)unit
keepAliveTime的单位。
默认情况下,只有当前线程池中的线程数大于corePoolSize时,keepAiveTime才会其作用,直到线程池中的线程数不大于corePoolSize。
(5)workQueue(阻塞队列)
任务队列,被提交但尚未被执行的任务。
(6)threadFactory
表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
(7)handler
拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)
① AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。
② CallerRunsPolicy:调用者运行,既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者。
③ DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列尝试再次提交当前任务。
④ DiscardPolicy:直接丢弃任务,不与任何处理也不抛异常。如果允许任务丢失,这是最好的方案。
2、线程池工作原理
(1)在创建了线程池后,等待提交过来的任务请求;
(2)当调用execute()方法添加一个请求任务时,线程池会做如下判断:
① 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
② 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
③ 如果这个时候队列满了且正在运行的线程数量小于maximumPoolSize,那么继续创建非核心线程立刻运行这个任务;
④ 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
(3)当一个线程完成任务时,它会从队列中取下一个任务来执行;
(4)当一个线程的空闲时间超过keepAliveTime的时候,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉;
所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。
3、实际线程池的使用
(1)为什么不能使用Executors去创建线程?
Executors返回的线程池对象的弊端为:
① FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
② CachedThreadPool和ScheduledThreadPool:允许的创建线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
(2)实际使用ThreadPoolExecutor的方式创建
① 自定义线程池的方式
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
② 如何合理设置线程池
实际上建议根据实际性能测试后得出。
-
CPU密集型:该任务需要大量的运算,且没有足额色,CPU一直全速运行。
一般公式:CPU核数+1个线程的线程池。
核数获得的方法:Runtime.getRuntime().availableProcessors()
-
IO密集型:该任务需要大量的IO,即大量的阻塞。
参考公式:CPU核数/(1-阻塞系数),阻塞系数位于0.8~0.9之间。