线程池:
JDK的线程池有:ThreadPoolExecutor,ScheduledThreadPoolExecutor(带任务调度)
ThreadPoolExecutor构造方法参数说明:ThreadPoolExecutor(核心线程数,最大线程数,救急线程存活时间,存活时间单位,阻塞队列),
救急线程数=最大线程数-核心线程数,当任务数多于线程数时,如果有救急线程,就会创建救急线程,如果没有就会进入阻塞队列等待.
创建线程池一般不用构造方法,用JDK提供的工厂方法,这些工厂方法也是调构造方法创建的线程池,比如
1 创建固定大小的线程池:
ExecutorService pool = Executors.newFixedThreadPool(2);固定大小线程池适合任务数固定的情况
底层实现:return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
1.如果线程数量没有达到“固定数量”,则每次提交一个任务池内就创建一个新的线程,直到到达固定的数量
2.线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3.如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。
使用场景:需要任务长期执行的场景。“固定数量的线程池”的线程数能够比较稳定保证一个数,能够避免频繁回收线程和创建线程,故适用于处理 CPU 密集型的任务,在 CPU 被工作线程长时间使用的情况下,能确保尽可能少的分配线程。 弊端:内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,使服务器资源迅速耗尽。
2 创建单一线程的线程池:ExecutorService pool = Executors.newSingleThreadExecutor(),这种线程池和自己创建单线程的区别:
某一个任务报错时单一线程的线程池会再创建一个线程继续执行下面的任务,单线程需要自己处理异常才会向下执行.
单一线程池的底层创建代码:
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
1.单线程化的线程池中的任务,是按照提交的次序,顺序执行的
2.池中的唯一线程的存活时间是无限的
3.当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的。
总体来说,单线程化的线程池所适用的场景是:任务按照提交次序,一个任务一个任务逐个执行的场景。
3 newCachedThreadPool 创建“可缓存线程池”
ExecutorService pool = Executors.newCachedThreadPool();
底层实现:return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,newSynchronousQueue<Runnable>());
1.在接收新的异步任务 target 执行目标实例时,如果池内所有线程繁忙,此线程池会添加新线程来处理任务。
2.此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
3.如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,那么就会回收空闲(60 秒不执行任务)线程。
适用场景:需要快速处理突发性强、耗时较短的任务场景,如 Netty 的NIO 处理场景、REST API 接口的瞬时削峰场景。“可缓存线程池”的线程数量不固定,只要有空闲线程就会被回收;接收到的新异步任务执行目标,查看是否有线程处于空闲状态,如果没有就直接创建新的线程。 弊端:线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能导致创线程过多会而导致资源耗尽。
线程池执行任务方法: pool.execute(Runnable接口)无返回值,pool.submit(Runnable接口)有返回值
线程池里核心线程是一直存活的,没有任务执行程序也不会结束,想要结束线程池需要调用pool.shutDown方法,调用这个方法后
正在执行的线程会继续执行完,阻塞队列里的任务也会执行完,但不会接受新的任务