线程池
3大方法、7大参数、4种拒绝 自定义线程池
1、3大方法
不推荐使用该3大方法,请使用ThreadPoolExecutor
创建线程池
Executors.newSingleThreadExecutor()
单个线程Executors.newFixedThreadPool(int num)
固定数线程的线程池Executors.newCacheThreadPool()
根据任务的情况,而规定线程池中的线程数
// newSingleThreadExecutor /* 只允许一个线程执行,当核心线程数全部都在正在执行(第一个参数),进来的其他任务将会被加入到LinkedBlockingQueue队列中,由于这里的LinkedBlockingQueue的最大长度为Integer.MAX_VALUE,所以是无限大,由于会无限将任务存储到队列中,可能会造成OOM,最终会导致系统瘫痪,不安全。 由于LinkedBlockingQueue可以很大,所以等待时间参数以及最大线程数的设置是无效的。 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } // newFixedThreadPool /* 只允许nThreads线程执行,当核心线程数全部都在正在执行(第一个参数),进来的其他任务将会被加入到LinkedBlockingQueue队列中,由于这里的LinkedBlockingQueue的最大长度为Integer.MAX_VALUE,所以是无限大,由于会无限将任务存储到队列中,可能会造成OOM,最终会导致系统瘫痪,不安全。 由于LinkedBlockingQueue可以很大,所以等待时间参数以及最大线程数的设置是无效的。 */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } // newCachedThreadPool /* 该方法,核心线程数为0,而可创建的最大线程数却为Integer.MAX_VALUE,无限创建线程,也会造成OOM,一个新线程会创建一个栈空间,而栈空间就会占内存。SynchronousQueue队列是一个不存储数据的队列,即放入即取出。当没有任务可以使用时,闲着的线程会等待60L秒后,如果还是没有任务执行,这个线程就会被销毁关闭。 */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
通过3大方法,查看源码,发现这3大方法都new ThreadPoolExecutor
该对象去创建线程池。阿里java开发手册强制规定,不可以使用Excutors工具类去创建线程池,因参数值为Integer.MAX_VALUE
,最大线程支持21亿,这样高的并发量,会导致OOM (OutOfMemory内存溢出) 。阿里官方强烈推荐使用ThreadPoolExecutor
来创建线程池。
2、7大参数
ThreadPoolExecutor 类
public ThreadPoolExecutor(int corePoolSize, // 线程核心数(一直存在的线程数量) int maximumPoolSize, // 能创建的最大线程数 long keepAliveTime, // 设定除核心线程以外的线程,超过该时间未使用就关闭 TimeUnit unit, // 设定时间的单位 BlockingQueue<Runnable> workQueue // 阻塞队列,用于存储等待使用线程的任务 ThreadFactory threadFactory, // 固定 RejectedExecutionHandler handler // 拒绝超额任务的方式(4种) ) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
执行过程原理
任务一来,先检查核心线程是否已经被全部占用,如果没有全部占用,那么就使用核心线程数(核心线程数在初始化的时候就创建出来做准备的),如果已被全部占用,那么这个任务就会被加入到阻塞队列中。如果阻塞队列已满,那么就会创建新的线程,但是总线程数必须小于等于最大线程数。如果,最大线程数,每个线程都被占用,并且阻塞队列已满,那么就会根据拒绝方式返回不同的拒绝结果。
3、4种拒绝
// 最大限度任务:maximumPoolSize + workQueue new ThreadPoolExecutor.AbortPolicy(); // 拒绝超过最大限度任务,并抛出异常 new ThreadPoolExecutor.CallerRunsPolicy(); // 哪儿来的任务回哪儿去,让你的原线程执行 new ThreadPoolExecutor.DiscardPolicy(); // 丢弃超过最大限度任务,不抛出异常 new ThreadPoolExecutor.DiscardOldestPolicy(); // 进来的线程任务,尝试与最早的任务竞争,竞争不到,丢弃,不抛出异常
4、自定义线程池
public class PoolTest { public static void main(String[] args) { // 获取该运行时,CPU的核数,作为最大线程数 int processors = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 1, processors, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); for (int i = 1; i <= 8; i++) { poolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName() + " OK"); }); } poolExecutor.shutdown(); } }
输出:
pool-1-thread-2 OK pool-1-thread-3 OK pool-1-thread-1 OK main OK pool-1-thread-3 OK pool-1-thread-4 OK pool-1-thread-2 OK main OK
CPU密集型 IO密集性 混合型
线程池最大线程数应该如何定义?
第一种:
CPU密集型:最大线程数应该等于CPU核数+1,这样最大限度提高效率。
// 通过该代码获取当前运行环境的cpu核数
Runtime.getRuntime().availableProcessors();
第二种:
IO密集型:主要是进行IO操作,执行IO操作的时间较长,这时cpu出于空闲状态,导致cpu的利用率不高。线程数为2倍CPU核数。当其中的线程在IO操作的时候,其他线程可以继续用cpu,提高了cpu的利用率。
第三种:
混合型:如果CPU密集型和IO密集型执行时间相差不大那么可以拆分;如果两种执行时间相差很大,就没必要拆分了。
第四种(了解):
在IO优化中,线程等待时间所占比越高,需要线程数越多;线程cpu时间占比越高,需要越少线程数。因此:
最佳线程数目=((线程等待时间+线程cpu时间)/ 线程cpu时间)* cpu数目