学习Java必定绕不开并发相关的主题,而线程相关的技术则是Java并发编程的核心之一。Java原生支持了线程(Thread),使用起来也非常简单,在Java中通过 new Thread()
即可在当前主线程下创建一个子线程,JVM会在调用该实例的 start()
方法后对应一个操作系统级别的线程,这样就完成了一个线程的创建。但是在大厂的开发规范例如著名的<<阿里巴巴Java开发手册>>中就明确提到了禁止显示的创建线程,也就是上述的使用 new Thread()
的方式,而是推荐使用线程池的方式创建线程。
为什么要使用线程池
在说使用线程池的好处之前可以先说明一下直接创建线程的坏处是什么,主要有以下几点:
- 线程的创建和销毁开销非常大,每次使用都重新创建线程会增加系统负担,增大响应时间;
- 手动创建线程不方便进行管理,如果没有对线程数量进行控制会导致创建大量线程消耗系统内存等资源(每个线程需要独立分配栈空间,默认是1M,具体大小取决于JDK版本和OS),另外线程过多也会导致频繁切换线程浪费CPU资源;
- 线程创建在不同平台上并不统一,如不同平台和不同OS下创建线程的最大限制不同。
使用线程池则在一定程度上可以避免上述问题,带来一系列的好处:
- 降低资源消耗:通过池化技术复用线程,降低线程创建和销毁的开销;
- 提高响应速度:减小获得线程的时间,提高响应速度;
- 提高线程的可管理性:基于线程池可以对线程进行统一的分配、调优和监控;
- 提供额外的功扩展能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
Java的Executor框架
Java中线程池实现是JUC中的Executor框架,其核心实现类是ThreadPoolExecutor。
参数解析
ThreadPoolExecutor构造函数比较复杂,完整的参数有7个,具体如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
复制代码
- corePoolSize, 核心线程数,当提交任务到线程池如果当前线程数小于核心线程数,会直接创建一个新的线程用于执行该任务;
- maximumPoolSize,最大线程数,指线程池所能创建的最大线程数;
- keepAliveTime,线程空闲后存活时间,超时后线程会被销毁,只有线程池中线程数量大于corePoolSize或设置了allowCoreThreadTimeOut()(允许空闲核心线程超时)才会生效;
- unit,线程存活时间单位;
- workQueue,任务队列,用于保存等待执行的任务的阻塞队列;
- threadFactory,线程工厂,用于创建线程并且可以为线程设置相关的属性,例如设置线程名称,是否是守护线程;
-
handler,拒绝策略,在线程池超过最大线程数并且任务队列也满了则会执行拒绝策略,Java默认提供了4种拒绝策略,分别是
- AbortPolicy:直接抛出RejectedExecutionException异常,这也是线程池默认的策略;
- CallerRunsPolicy: 使用调用者所在的线程来执行该任务,一般是主线程,需要注意的是该策略会打乱任务投递的执行顺序(该任务会先于队列中其他任务执行);
- DiscardOldestPolicy: 丢弃掉任务队列中最先进入(也就是原本要执行的下一个任务)的任务,并执行当前任务;
- DiscardPolicy:不进行处理直接丢弃当前任务。
由此可见,这四种拒绝策略都存在一些问题,因此在实际使用中我们可能需要自己实现RejectedExecutionHandler。
原文
https://juejin.im/post/5ef01150f265da02c94e1593