简介
线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间,在线程销毁时需要回收这些系统资源。频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险,而且当服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务?这是线程自身无法解决的。所以需要通过线程池协调多个线程,并实现主次线程隔离、定时执行、周期执行等任务
作用
- 利用线程池管理并复用线程、控制最大并发数等
- 实现任务线程队列缓存策略和拒绝机制
- 实现定时执行、周期执行等与时间相关的功能
- 隔离线程环境(根据业务独立配置线程池,避免相互影响)
创建
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:常驻核心线程数。如果等于0,则任务执行完成后没有任何请求进入时销毁线程池的线程;如果大于0,即使本地任务执行完成,核心线程也不会被销毁
- maximumPoolSize:线程池能够容纳同时执行的最大线程数。必须大于等于1,且必须大于等于corePoolSize
- keepAliveTime:线程池中的线程空闲时间,当空闲时间达到keepAliveTime的值时,线程会被销毁,直到只剩下corePoolSize个线程为止,避免浪费内存和句柄资源。默认情况下,当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,但是当java.util.concurrent.ThreadPoolExecutor#allowCoreThreadTimeOut变量设置为true时,核心线程超时后也会被回收
- unit:时间单位。keepAliveTime的时间单位通常是java.util.concurrent.TimeUnit#SECONDS
- workQueue:缓存队列。当请求的线程数大于corePoolSize时,线程进入BlockingQueue阻塞队列,BlockingQueue队列缓存达到上限后,如果还有新任务需要处理,那么线程池会创建新的线程,最大线程数为maximumPoolSize
- threadFactory:线程工厂。用来生产一组相同任务的线程
- handler:执行拒绝策略的对象。当workQueue的任务缓存区到达上限后,并且活动线程数达到maximumPoolSize时,线程池通过该策略处理请求
拒绝
- CallerRunsPolicy:提交任务的线程自己执行该任务
- AbortPolicy:直接抛出异常(throws RejectedExecutionException)
- DiscardPolicy:直接丢弃任务
- DiscardOldestPolicy:丢弃最早进入队列的任务,接收新任务
定制
自定义ThreadFactory:给线程赋予一个有意义的名字
class UserThreadFactory implements ThreadFactory {
private static String namePrefix;
private static ThreadGroup threadGroup;
private static AtomicInteger nextId = new AtomicInteger(1);
public UserThreadFactory(String whatFeatureOfGroup) {
namePrefix = "UserThreadFactory's " + whatFeatureOfGroup + "-Worker-";
final SecurityManager securityManager = System.getSecurityManager();
threadGroup = Objects.nonNull(securityManager) ? securityManager.getThreadGroup()
: Thread.currentThread().getThreadGroup();
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
return new Thread(threadGroup, task, name, 0);
}
}
自定义拒绝策略
class UserRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// todo 自定义的策略
log.warn("Task rejected. Executor Info:{}", executor.toString());
}
}
注意
1,为什么不推荐使用Executors快速创建线程池?因为Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM
2,谨慎使用默认拒绝策略。线程池默认的拒绝策略会直接抛出运行时异常(throw RejectedExecutionException),建议自定义拒绝策略