一、线程池
什么是线程池:
线程池主要是
控制运行的线程的数量
,处理过程中将任务加入队列
,然后在线程创建后启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候
,等其他线程执行完毕,再从队列中取出任务来执行.
作用:
1)线程复用
2)控制最大并发数
3)管理线程
优点:
1)降低资源消耗
重复利用已创建的线程,降低线程创建和销毁造成的消耗
2)提高响应速度
任务到达时,不需要创建线程,就能立即执行
3)提高线程的可管理性
线程是稀缺资源,无限创建会消耗系统资源,降低系统稳定性,使用线程池可以统一分配、调优、监控
二、线程池生命周期
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
ctl:
表示线程池的运行状态(runState)和线程池中有效线程的数量(workCount)
使用了Integer类型来保存,高3位保存runState,低29位保存workerCount
RUNNING:
能接受新提交的任务,也能处理阻塞队列中的任务SHUTDOWN:
不再接受新提交的任务,可以继续处理阻塞队列中已保存的任务
1)在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态
2) finalize() 方法在执行过程中也会调用shutdown()方法进入该状态STOP:
不接受新的任务,也不处理阻塞队列中的任务,会中断正在处理任务的线程
1)在线程池处于 RUNNING 或SHUTDOWN状态时,调用 shutdownNow()方法会使线程池进入到该状态TIDYING:
当所有的任务都已终止,workCount(有效线程数为0),线程池进入该状态后,调用terminated()方法进入TERMINATED状态TERMINATED:
在terminated()方法执行完进入该状态
进入TERMINATED状态条件:
1)线程池不是RUNNING、TIDYING、TERMINATED状态
2)线程池是SHUTDOWN并且workerQueue为空
3)workCount为0
4)设置TIDYING成功
三、线程池的基本参数
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;
}
corePoolSize:
常驻核心线程数maximumPoolSize:
最大同时执行线程数,>=1keepAliveTime:
多余空闲线程存活时间,当线程池的数量超过poolSize,线程空闲时间达到keepAliveTime,空闲的线程会被销毁,直到剩余线程数量为corePoolSizeunit:
keepAliveTime的单位workQueue:
任务队列,提交但尚未被执行的任务threadFactory:
创建新线程的线程工厂,默认即可handler:
拒绝策略,当线程队列满了,并且工作线程>=线程池最大线程数(corePoolSize>=maximumPoolSize)时,拒绝请求执行的runnable策略
四、实现方式
Execuotrs.newFixedThreadPool(int n):
定长线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 特点:
1)定长线程池,控制最大并发数,超出的线程会在队列中等待
2)corePoolSize和maximumPoolSize是相等
3)使用LinkedBlockQueue作为阻塞队列
Executors.newSingleThreadExecutor():
单线程化线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 特点:
1)用唯一的工作线程来执行任务
2)corePoolSize和maximumPoolSize都为1
3)使用LinkedBlockQueue作为阻塞队列(由链表组成的有界阻塞队列)
Executors.newCacheThreadPool():
可缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 特点:
1)如果线程池长度超过处理需要,回收空闲线程,如果不足,则创建新线程
2)corePoolSize为0和maximumPoolSize为int的最大值
3)使用SynchronousQueue作为阻塞队列(不存储元素的阻塞队列,每个put操作必须等待一个take操作,否则不能添加元素)
4)来任务了就创建线程,线程空闲超过60s就销毁线程
五、workQueue
workQueue:等待队列,当线程池中的线程数量>=corePoolSize时,新提交的任务,会被封装为一个Worker对象放入等待队列,有以下处理方式:
LinkedBlockingQueue:
无界队列
线程池中能够创建的最大线程数就是corePoolSize,maximumPoolSize不会起作用。适用于FixedThreadPool与SingleThreadExcutor。基于链表的阻塞队列,创建的线程数不会超过corePoolSizes(maximumPoolSize值与其一致),当线程池中所有的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。
按照FIFO原则对元素进行排序,吞吐量高于ArrayBlockingQueue
ArrayListBlockingQueue:
有界队列
使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能够
降低资源的消耗(CPU使用率、操作系统资源的消耗、上下文环境切换的开销)
,但也使得线程池对线程的调度变得更困难
。队列大小和最大池大小可能需要相互折衷。
设置较大的队列容量和较小的线程池容量:
优点:降低资源消耗
缺点:降低线程处理任务的吞吐量设置较小的队列容量和较大的线程池容量:
优点:CPU使用率会提高
缺点:当设置的线程池数量较大,同时提交的任务数较多,线程调度变困难,反而可能降低处理任务的吞吐量
SynchronousQueue:
直接提交策略
适用于CachedThreadPool。它将任务直接提交给线程而不保持它们。如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程
六、拒绝策略
以下内置策略均实现了RejectExecutionHandler接口
ThreadPoolExcutor.AbortPolicy():
直接抛出异常,默认策略,阻止系统正常运行ThreadPoolExcutor.CallerRunsPolicy():
"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是让调用者所在的线程执行任务ThreadPoolExcutor.DiscardOldersPolicy():
抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交ThreadPoolExcutor.DiscardPolicy():
不处理,直接丢弃
七、为什么要手动创建线程池?
参考阿里巴巴java开发手册
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
八、如何合理配置线程池参数
CPU密集型:
任务需要大量运算,没有阻塞
线程池数量建议为:CPU核数+1CPU密集型:
任务需要大量I/O,频繁阻塞,大量线程被阻塞,所以需要多配置线程数
线程池数量建议为:CPU核数/(1-阻塞系数),阻塞系数一般为0.8~0.9
或者线程数为cpu核数*2
九、死锁编码及定位分析
jps定位线程id
jstack 线程id,查看死锁异常信息