文章目录
- 前言
- 1. 为什么要使用线程池?
- 2. 创建线程池
- 3. 线程池工作流程
- 4. Runnable和Callable
- 4. 正确使用线程池
- 4.1 避免使用无界队列
- 4.2 选择合适的拒绝策略
- 4.3 处理异常
- 4.4 获取结果
- 项目推荐
Java线程池使用
前言
创建定长线程池事例:
ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(),
new ThreadFactory() {
private final AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("thread-" + threadNum.getAndIncrement());
// 捕获运行时异常
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("线程" + thread.getName() + " 运行出错:" + e);
});
return thread;
}
});
1. 为什么要使用线程池?
- 降低创建/销毁线程的系统开销
- 资源限制和管理,防止线程并发数量过多,抢占系统资源从而导致阻塞
2. 创建线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 核心线程数
- maximumPoolSize 最大线程数
- keepAliveTime 空闲线程存活时间
- unit 空闲线程存活时间的单位
- workQueue 工作队列
- threadFactory 新线程属性设置
- handler 拒绝策略
handler拒绝策略
线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是运行时异常,容易忘记捕获。如果不关心任务被拒绝的事件,可以设置成DiscardPolicy
拒绝策略 | 拒绝行为 |
AbortPolicy | 抛出RejectedExecutionException |
DiscardPolicy | 什么也不做,直接忽略 |
DiscardOldestPolicy | 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置 |
CallerRunsPolicy | CallerRunsPolicy |
缓冲队列有类型
- 直接提交SynchronousQueue,是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
- 无界队列LinkedBlockingQueue,当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。
- 有界队列ArrayBlockingQueue,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。
常用线程池类型
- newCachedThreadPool:缓冲线程池,内部使用SynchronousQueue
- newFixedThreadPool:单线程
- newScheduledThreadPool:内部使用DelayedWorkQueue
- newSingleThreadExecutor
使用
比较重要的几个
类:
3. 线程池工作流程
- 如果运行的线程小于corePoolSize,则添加新线程来执行。
- 如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
- 如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。
corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略
4. Runnable和Callable
区别如下:
- 方法签名不同,void Runnable.run(), V Callable.call() throws Exception
- 是否允许有返回值,Callable允许有返回值
- 是否允许抛出异常,Callable允许抛出异常。
三种提交任务的方式:
提交方式 | 是否关心返回结果 |
Future submit(Callable task) | 是 |
void execute(Runnable command) | 否 |
Future<?> submit(Runnable task) | 否,虽然返回Future,但是其get()方法总是返回null |
4. 正确使用线程池
4.1 避免使用无界队列
不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,容易OOM,我们应该使用ThreadPoolExecutor的构造方法手动指定队列的最大长度:
ExecutorService executorService = new ThreadPoolExecutor(10, 10,
0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
new ThreadPoolExecutor.DiscardPolicy());
4.2 选择合适的拒绝策略
线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是运行时异常,容易忘记捕获。
4.3 处理异常
Future异常
调用Future.get()方法时获取,执行过程中的异常会被包装成ExecutionException,submit()方法本身不会传递结果和任务执行过程中的异常
ExecutorService executorService = Executors.newFixedThreadPool(4);
Future<Object> future = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
// 异常会在调用Future.get()时传递给调用者
throw new RuntimeException("exception is me");
}
});
try {
Object result = future.get();
} catch (InterruptedException e) {
// interrupt
} catch (ExecutionException e) {
// exception in Callable.call()
e.printStackTrace();
}
excute捕获运行时异常
多线程环境中,主线程通过try catch无法捕获子线程异常。
解决办法使用UncaughtExceptionHandler或者在子线程中try,catch处理。
ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(),
new ThreadFactory() {
private final AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("thread-" + threadNum.getAndIncrement());
// 捕获异常
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("线程" + thread.getName() + " 运行出错:" + e);
});
return thread;
}
});
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
// try{
System.out.println(1/0);
// }catch (Exception e){
// System.out.println("asdasdasd");
// }
}
});
以上代码运行结果:
线程thread-1 运行出错:java.lang.ArithmeticException: / by zero
4.4 获取结果
获取单个结果
过submit()向线程池提交任务后会返回一个Future,调用V Future.get()方法能够阻塞等待执行结果,V get(long timeout, TimeUnit unit)方法可以指定等待的超时时间。
获取多个结果
使用ExecutorCompletionService,该类的take()方法总是阻塞等待某一个任务完成,然后返回该任务的Future对象。向CompletionService批量提交任务后,只需调用相同次数的CompletionService.take()方法,就能获取所有任务的执行结果,获取顺序是任意的,取决于任务的完成顺序
事例:
Executor executor = Executors.newFixedThreadPool(3);
CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(executor);
for (int i = 0; i < 10; i++) {
cs.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Random r = new Random();
int init = 0;
for (int i = 0; i < 100; i++) {
init += r.nextInt();
Thread.sleep(100);
}
return Integer.valueOf(init);
}
});
}
for (int i = 0; i < 10; i++) {
try {
Future<Integer> future = cs.take();
if (future != null) {
System.out.println(future.get());
}
} catch (Exception e) {
System.out.println("error");
}
}