线程池
1、线程池的创建
线程池可以通过ThreadPoolExecutor来创建,我们来看一下它的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
几个核心参数的作用:
- corePoolSize: 线程池核心线程数最大值
- maximumPoolSize: 线程池最大线程数大小
- keepAliveTime: 线程池中非核心线程空闲的存活时间大小
- unit: 线程空闲存活时间单位
- workQueue: 存放任务的阻塞队列
- threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
- handler: 线城池的饱和策略事件,主要有四种类型。
2、任务执行
线程池执行流程,即对应execute()方法:
- 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
- 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
- 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
- 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
3、线程池的工作队列
ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;
SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
4、四种拒绝策略
-
AbortPolicy(抛出一个异常,默认的)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i = 0; i < 10; i++) { try { threadPoolExecutor.submit(() -> { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); } catch (Exception e) { if (e instanceof RejectedExecutionException) { System.out.println("线程池满啦"); } } } threadPoolExecutor.shutdown(); }
- DiscardPolicy(直接丢弃任务)
-
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
-
CallerRunsPolicy(交给线程池调用所在的线程进行处理)
5、execute()方法和submit()
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(() -> System.out.println("11"));//适合适用于Runnable
Future<Integer> submit = service.submit(() -> 3);//适合使用于Callable 获取线程得执行结果
Integer integer = submit.get();
//3.关闭连接池
service.shutdown();
}
6、线程池满了怎么通知客户端
-
第一种方案就是创建线程池得时候使用默认得饱和策略AbortPolicy,然后再任务提交得地方捕获异常,再catch块里面通知客户端。
// 创建一个最大线程数为2的线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); // 同时提交10个任务 for (int i = 0; i < 10; i++) { try { threadPoolExecutor.submit(() -> { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); } catch (Exception e) { if (e instanceof RejectedExecutionException) { System.out.println("线程池满啦"); // 通知客户端 } } } threadPoolExecutor.shutdown();
-
上述方式捕获异常之后,相当于时任务丢弃了,实际工作中这种方案肯定时不可取的,查阅相关资料好像没有好的解决方案,笔者在网上看到有重写饱和策略的,因为不能丢弃任务,因此可以参考CallerRunsPolicy策略的实现方案,丢给主线程执行,并通知到客户端,大家有啥想法也可以一起讨论。
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new ThreadPool.MyRejectedExecutionHandler());
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(() -> {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
threadPoolExecutor.shutdown();
}
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
public MyRejectedExecutionHandler() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
//通知客户端
System.out.println("线程池满了");
}
}
}
参考