单线程池
- newSingleThreadExecutor创建
池中保持一个线程,最多也只有一个线程,也就是说这个线程池是顺序执行任务的,多余的任务就在队列中排队。
固定线程池
- newFixedThreadPool(nThreads)创建
池中保持nThreads个线程,最多也只有nThreads个线程,多余的任务也在队列中排队。
线程数固定且线程不超时。
缓存线程池
- newCachedThreadPool()创建
池中不保持固定数量的线程,而是按需创建,最多可创建Integer.MAX_VALUE个线程,这已大大超过目前任何os允许的线程数。
空闲的线程最多保持60s,多余的任务在SynchronousQueue等待。
适用场景
- 耗时较短的任务
- 任务处理速度 > 任务提交速度 ,这样才能保证不会不断创建新的进程,避免内存被占满。
线程池中的线程是被线程池缓存了的,即:
线程没有任务执行时,便处于空闲状态,处于空闲状态的线程并不会被立即销毁(会被缓存),只有当空闲时间超出一段时间(默认60s)后,线程池才会销毁该线程(相当于清除过时缓存)
新任务到达后,线程池首先会让被缓存住的线程(空闲状态)去执行任务,若无可用线程(无空闲线程),便会创建新的线程。
为什么使用SynchronousQueue()?
因为单线程池和固定线程池中,线程数量有限,因此提交的任务需要在LinkedBlockingQueue队列中等待空闲线程。
而缓存线程池中,线程数量几乎无限(上限为Integer.MAX_VALUE),因此提交的任务只需要在SynchronousQueue队列中同步移交给空余线程即可。
固定调度线程池
- newScheduledThreadPool(n)创建
池中保持n个线程,多余的任务在DelayedWorkQueue中等待。
有一项技术可以缓解执行时间较长任务造成的影响,即限定任务等待资源的时间,而不要无限的等待.
先看第一个例子,测试单线程池、固定线程池和缓存线程池(注意增加和取消注释):
public class ThreadPoolExam { public static void main(String[] args) { //first test for singleThreadPool ExecutorService pool = Executors.newSingleThreadExecutor(); //second test for fixedThreadPool // ExecutorService pool = Executors.newFixedThreadPool(2); //third test for cachedThreadPool // ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { pool.execute(new TaskInPool(i)); } pool.shutdown(); } } class TaskInPool implements Runnable { private final int id; TaskInPool(int id) { this.id = id; } @Override public void run() { try { for (int i = 0; i < 5; i++) { System.out.println("TaskInPool-["+id+"] is running phase-"+i); TimeUnit.SECONDS.sleep(1); } System.out.println("TaskInPool-["+id+"] is over"); } catch (InterruptedException e) { e.printStackTrace(); } } }
如图为排查底层公共缓存调用出错时的截图
- 有意义的线程命名
绿色框采用自定义的线程工厂,明显比蓝色框默认的线程工厂创建的线程名称拥有更多的额外信息:如调用来源、线程的业务含义,有助于快速定位到死锁、StackOverflowError 等问题。
Executors类提供的一些快捷声明线程池的方法虽然简单,但隐藏了线程池的参数细节。因此,使用线程池时,我们一定要根据场景和需求配置合理的线程数、任务队列、拒绝策略、线程回收策略,并对线程进行明确的命名方便排查问题。