创建线程的代价是昂贵的,还会给GC带来压力,如果频繁创建线程那么GC的时候也需要回收对应的线程资源。使用线程池,一方面可以提升线程的使用率,减少对象的创建、销毁;另外,线程池还可以有效控制线程数,提升服务器的使用资源,避免因线程使用不当导致资源不足而发生宕机等问题。
示例代码如下:
public static final String THREAD_NAME_PREFIX = "listen-async-send-thread-"; private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 32, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(10000), new ThreadFactory() { private AtomicInteger id = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(THREAD_NAME_PREFIX + id.addAndGet(1)); return thread; } }, new ThreadPoolExecutor.CallerRunsPolicy()); public void listen(HibernateChangeEntity changeEntity) { executor.execute(new Runnable() { @Override public void run() { try { if (Thread.currentThread().getName().startsWith(THREAD_NAME_PREFIX)) { // 构造线程上下文 } // 业务代码部分 } catch (Throwable e) { } finally { if (Thread.currentThread().getName().startsWith(THREAD_NAME_PREFIX)) { // 清理线程上下文的信息 } } } }); }
建议及注意事项:
1、设定线程名的统一前缀,便于跟踪查看及问题排查
2、合适的初始线程数、最大线程数及任务队列容量(只有超过队列容量,池才会在coreSize基础上创建新的线程)
3、设置及清理线程上下文等信息(因为线程会被复用,如果不进行清理,会影响后续业务逻辑和造成内存泄露等问题)
4、捕捉所有异常并做按需处理
5、ThreadPoolExecutor执行顺序
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,拒绝任务,触发rejectedExecutionHandler对应的策略
6、线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
ThreadPoolExecutor类有几个内部实现类来处理这类情况:
1、AbortPolicy 丢弃任务,抛运行时异常
2、CallerRunsPolicy 回到主线程执行任务
3、DiscardPolicy 忽视,什么都不会发生
4、DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
另外,就是实现RejectedExecutionHandler接口,可自定义处理器
参考资料: