🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## **什么是线程池?** 线程池可以理解为执行特点数量线程的管理容器 ## **线程池作用** * **降低资源消耗** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 * **提高响应速度** 当任务到达时,任务可以不需要等到线程创建就能立即执行。 * **提高线程的可管理性** 使用线程池可以进行统一分配、调优和监控 ## **线程池的创建方式** Java通过Executors(jdk1.5并发包)提供四种线程池 * newCachedThreadPool 创建一个相对无限大可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程 ``` ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; newCachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }); } ``` 源码分析 ``` public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); } ``` newCachedThreadPool方法只是对ThreadPoolExecutor方法做了简单的封装,其中 1.corePoolSize:核心线程数为0 2.maximumPoolSize:最大线程数为最大整数值Integer.MAX\_VALUE,可以理解为无限大 3.keepAliveTime:空闲等待销毁时间为60S 4.workQueue:线程缓存队列采用的是SynchronousQueue单元素阻塞队列 总结:核心线程数小于最大线程数且为0,所以任务完成后60S所有线程都会销毁,因为采用的是SynchronousQueue单元素阻塞队列,队列无法缓存多个任务,所以多任务时会不停的创建线程。 * newFixedThreadPool 创建一个固定长度的线程池,可控制线程最大并发数,超出的线程会在队列中等待 ``` ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; newFixedThreadPool.execute(new Runnable() { @Override public void run() { ... Thread.sleep(100); ... System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }); } ``` 执行结果如下、并且程序不会退出 ``` pool-1-thread-1,i:0 pool-1-thread-2,i:1 pool-1-thread-3,i:2 pool-1-thread-1,i:3 pool-1-thread-2,i:4 pool-1-thread-3,i:5 pool-1-thread-1,i:6 pool-1-thread-2,i:7 pool-1-thread-3,i:8 pool-1-thread-1,i:9 ``` 源码分析 ``` public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } ``` newFixedThreadPool方法也只是对ThreadPoolExecutor方法做了简单的封装,其中 1.corePoolSize:核心线程数为用户设定的值 2.maximumPoolSize:最大线程数为等于核心线程数 3.keepAliveTime:空闲等待销毁时间为0S 4.workQueue:线程缓存队列采用的是LinkedBlockingQueue队列 总结:核心线程数等于最大线程数、所以线程池在创建三个线程后会一直维持,等待的任务保存在LinkedBlockingQueue队列中,任务完成后线程池维持3个线程等待任务,剩余其他线程销毁。 * newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行 ``` ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; newScheduledThreadPool.schedule(new Runnable() { @Override public void run() { ... Thread.sleep(100); ... System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }, 3, TimeUnit.SECONDS); } ``` 表示延迟3秒处理 源码分析 ``` public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService { ... public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); ... } ``` ScheduledThreadPoolExecutor也是继承ThreadPoolExecutor了方法,其中 1.corePoolSize:核心线程数为用户设定的值 2.maximumPoolSize:最大线程数为最大整数值Integer.MAX\_VALUE,可以理解为无限大 3.keepAliveTime:空闲等待销毁时间为0S 4.workQueue:线程缓存队列采用的是专门的DelayedWorkQueue队列 总结:线程可无限创建,但是当任务数量小于核心线程数时,多余的线程都会第一时间被销毁,只保留核心线程数量值的线程。 * newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行 ``` ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int temp = i; newSingleThreadExecutor.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }); } ``` 源码分析 ``` public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } ``` 底层使用的还是ThreadPoolExecutor的构造方法,其中 1.corePoolSize:核心线程数为1 2.maximumPoolSize:最大线程数为1 3.keepAliveTime:空闲等待销毁时间为0S 4.workQueue:缓存队列采用的是LinkedBlockingQueue队列 总结:只创建并且保持一个线程在执行任务,所有等待任务保存在LinkedBlockingQueue队列中 总结上面4中创建线程池的方法,似乎发现最终执行的还是ThreadPoolExecutor的构造方法,只是构造参数不同而已 * corePoolSize 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中 * maximumPoolSize 线程池最大线程数,它表示在线程池中最多能创建多少个线程 * keepAliveTime 最大连接数大于核心线程数时,空闲等待的线程待销毁的时间 * workQueue 缓存队列,线程池中线程数量大于核心线程数时,任务存入次队列中 ## **线程池原理分析** 线程池的处理流程 ![](https://img.kancloud.cn/79/86/79868da81af0c67d5a4347d933c14352_776x452.jpg) ## **自定义线程池** 创建线程任务 ``` class TaskThread implements Runnable { private String name; public TaskThread(String name) { this.name = name; } @Override public void run() { System.out.println(Thread.currentThread().getName() + name); } } ``` 自定义线程池 ``` class test { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5)); for (int i = 0; i < 10; i++) { executor.execute(new TaskThread("任务" + i)); } executor.shutdown(); } } ``` 自定义线程中 1.corePoolSize:核心线程数为1 2.maximumPoolSize:最大线程数为2 3.keepAliveTime:空闲等待销毁时间为60S 4.workQueue:缓存队列采用的是ArrayBlockingQueue长度为5的有界队列 执行结果 ``` java.util.concurrent.RejectedExecutionException... pool-1-thread-2任务6 pool-1-thread-2任务1 pool-1-thread-2任务2 pool-1-thread-2任务3 pool-1-thread-2任务4 pool-1-thread-2任务5 pool-1-thread-1任务0 ``` 最终执行了7次的任务,最后抛出RejectedExecutionException异常警告,那就来分析一下原因吧。 根据线程池的执行流程可分析 1.核心线程数为1,线程池会创建一个线程执行一个任务任务 2.当任务数量大于核心线程数量时,线程池会将未被分配执行的任务保存待缓存队列中,因为缓存队列是初始化长度为5的有界队列,所有只能缓存5个任务。 3.缓存队列存储满了之后,线程池判断最大线程数,发现最大线程数为2,因为当前只创建了1个线程,所有线程池会再去创建一个线程去执行一个任务。 4.到最后线程池发现最大的线程数已经满了,但是还是有任务需要执行,于似乎直接拒绝处理抛出异常。 ## **合理配置线程池** 需要根据任务的特性来分析 1. 任务的性质:CPU密集型、IO密集型和混杂型 2. 任务的优先级:高中低 3. 任务执行的时间:长中短 4. 任务的依赖性:是否依赖数据库或者其他系统资源 * CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数 * IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1 * 混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了 * 前人总结的 最佳线程数=((线程等待时间+线程CPU时间)/线程CPU时间 )\* CPU数目