- 程池
程序的运行,本质上就是占用系统的资源,我们要做的就是优化资源的使用,所以衍生出一种技术叫:池化技术 线程池 连接池 内存池 对象池,都是类似的概念
什么是池化技术呢?
简单来说,池化技术就是:提前准备好一些资源,就来拿来使用,用完之后还回来,以供下次使用,这种思想可以是CPU效率更高
线程池的好处?
- 降低资源的消耗
- 提高响应的速度(不用频繁的创建销毁,使用完之后并不会进行销毁,而是返回到线程池,以便下次使用)
- 由于把线程都放在线程池中了,可以方便管理
总结来说:线程池的好处就是线程可以复用且方便管理,可以控制最大并发数
线程池该怎么使用呢?
- 创建线程池
Executors.newxxx();
- 使用线程池创建线程
execute 使用线程池创建线程使用execute,丢弃之前的new Thread
- 关闭线程池 shutDown();
一般配合try catch finally使用 try中写创建线程池和业务,finally中关闭线程池
//创建线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); //使用线程池创建线程 executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); //创建一个单例线程 ExecutorService executorService = Executors.newSingleThreadExecutor(); //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 try { for (int i = 0; i < 10; i++) { executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); }
简单来说就是:3大方法 7大参数 4种拒绝策略
线程池不允许使用Executors去创建(Executors其实就是个工具类,3大方法都和Executors有关),而是通过ThreadPoolExecutor的方式去创建,这样的处理方式可以更加明确线程池的运行规则,避免资源耗尽的风险(OOM内存溢出)
3大方法
package com.wyh.ThreadPool; import java.util.concurrent.Executors; /** * @program: JUC * @description: 线程池 * @author: 魏一鹤 * @createDate: 2022-02-28 22:14 **/ public class ThreadPoolDemo01 { public static void main(String[] args){ //Executors其实就是个工具类,3大方法都在它里面 //3大方法 1 newSingleThreadExecutor得到一个单一的线程池 这个线程池只有一个线程处理 Executors.newSingleThreadExecutor(); //3大方法 2 newFixedThreadPool得到一个固定的线程池大小 参数就是线程池大小 Executors.newFixedThreadPool(5); //3大方法 3 newFixedThreadPool得到一个缓存的线程池 可以伸缩的 遇强则强遇弱则弱 Executors.newCachedThreadPool(); } }
1 单一的线程池
Executors.newSingleThreadExecutor(); //得到一个单一的线程池 这个线程池只有一个线程处理
package com.wyh.ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @program: JUC * @description: 线程池 * @author: 魏一鹤 * @createDate: 2022-02-28 22:14 **/ public class ThreadPoolDemo01 { public static void main(String[] args){ //创建一个单例线程 ExecutorService executorService = Executors.newSingleThreadExecutor(); //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 try { for (int i = 0; i < 10; i++) { //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); } } }
通过结果发现线程池中的线程被一个线程调用 这就是单例线程池
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
pool-1-thread-1ok
2 固定的线程池
Executors.newFixedThreadPool(5); //得到一个固定的线程池大小 参数就是线程池大小
package com.wyh.ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @program: JUC * @description: 线程池 * @author: 魏一鹤 * @createDate: 2022-02-28 22:14 **/ public class ThreadPoolDemo01 { public static void main(String[] args){ //创建一个固定个数的线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 try { for (int i = 0; i < 10; i++) { //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); } } }
pool-1-thread-2ok
pool-1-thread-4ok
pool-1-thread-3ok
pool-1-thread-6ok
pool-1-thread-1ok
pool-1-thread-5ok
pool-1-thread-8ok
pool-1-thread-7ok
pool-1-thread-9ok
pool-1-thread-10ok
通过结果发现线程池中的线程被多个线程并发调用,最多的并发数是我创建线程池时生命的线程个数 这就是固定个数线程池
就算我用10个线程并发执行,但是创建线程池个数就是1,那这10个线程也是被这被一个线程并发执行,这个思想类似于单例线程池,不管并发量多少都是一个线程并发执行
package com.wyh.ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @program: JUC * @description: 线程池 * @author: 魏一鹤 * @createDate: 2022-02-28 22:14 **/ public class ThreadPoolDemo01 { public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(1); try { for (int i = 0; i < 10; i++) { //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); } } }
3 可缓存的线程池
Executors.newCachedThreadPool(); //得到一个缓存的线程池 可以伸缩的 遇强则强遇弱则弱
package com.wyh.ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @program: JUC * @description: 线程池 * @author: 魏一鹤 * @createDate: 2022-02-28 22:14 **/ public class ThreadPoolDemo01 { public static void main(String[] args){ //创建一个可缓存的线程池 会随着线程个数提高并发量 遇强则强遇弱则弱,强弱根据线程数决定 ExecutorService executorService = Executors.newCachedThreadPool(); try { for (int i = 0; i < 50; i++) { //使用线程池创建线程的话 就不使用new Thread了 而且使用execute来创建线程 executorService.execute(()->{ System.out.println(Thread.currentThread().getName() + "ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池使用完 使用shutDown()关闭线程池 executorService.shutdown(); } } }
通过结果发现线程池中的线程并发量会随着线程个数提高并发量 遇强则强遇弱则弱,强弱根据线程数决定,这就是缓存线程池
pool-1-thread-2ok
pool-1-thread-5ok
pool-1-thread-6ok
pool-1-thread-4ok
pool-1-thread-8ok
pool-1-thread-1ok
pool-1-thread-3ok
pool-1-thread-9ok
pool-1-thread-5ok
pool-1-thread-7ok
pool-1-thread-7ok
pool-1-thread-5ok
pool-1-thread-9ok
pool-1-thread-2ok
pool-1-thread-1ok
pool-1-thread-10ok
pool-1-thread-3ok
pool-1-thread-6ok
pool-1-thread-4ok
pool-1-thread-8ok
pool-1-thread-11ok
pool-1-thread-3ok
pool-1-thread-10ok
pool-1-thread-13ok
pool-1-thread-1ok
pool-1-thread-9ok
pool-1-thread-1ok
pool-1-thread-2ok
pool-1-thread-7ok
pool-1-thread-5ok
pool-1-thread-15ok
pool-1-thread-9ok
pool-1-thread-1ok
pool-1-thread-7ok
pool-1-thread-2ok
pool-1-thread-14ok
pool-1-thread-13ok
pool-1-thread-10ok
pool-1-thread-8ok
pool-1-thread-11ok
pool-1-thread-3ok
pool-1-thread-12ok
pool-1-thread-6ok
pool-1-thread-4ok
pool-1-thread-18ok
pool-1-thread-1ok
pool-1-thread-17ok
pool-1-thread-7ok
pool-1-thread-16ok
pool-1-thread-5ok
7大参数
无论是哪种线程池,本质都是调用ThreadPoolExecutor,它有7大参数
- corePoolSize 核心线程池大小
- maximumPoolSize 最大线程池大小
- long keepAliveTime 超时等待 (超时了没有还调用会释放)
- TimeUnit unit 超时时间单位
- BlockingQueue<Runnable> workQueue 阻塞队列
- ThreadFactory threadFactor 线程工厂 创建线程的 一般不用动
- RejectedExecutionHandler handler 拒绝策略
//本质 ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, //核心线程池大小 int maximumPoolSize, //最大线程池大小 long keepAliveTime, //超时等待 (超时了没有还调用会释放) TimeUnit unit, //超时时间单位 BlockingQueue<Runnable> workQueue, //阻塞队列 ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动 RejectedExecutionHandler handler //拒绝策略) { 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; }
自定义手动创建线程池
//使用ThreadPoolExecutor自定义线程池 它有7个参数 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//核心线程池大小 正常只有2个线程处理 5, //最大线程池大小 并发量大的时候最多处理的线程数 3, //等待时间 3 TimeUnit.SECONDS, //时间单位 秒 new LinkedBlockingDeque<>(3),//阻塞队列 存放的队列数 Executors.defaultThreadFactory(),//线程工程 Executors的defaultThreadFactory方法默认创建的线程工厂 一般不用变 new ThreadPoolExecutor.AbortPolicy());//拒绝策略 如果队列满了 还有队列进来 就不处理满了之后进来的队列,并且抛出异常
4种拒绝策略
默认的拒绝策略:Abortpolicy 如果队列满了 还有队列进来 就不处理满了之后进来的队列,并且抛出异常
1 Abortpolicy 如果队列满了 还有队列进来 就不处理满了之后进来的队列,并且抛出异常
2 CallerRunsPolicy 哪来的去哪里(打发)
3 DiscardPolicy 队列满了,丢掉多余的任务 不会抛出异常
4 DiscardOldestPolicy 队列满了,会尝试和旧的队列(最早加入的队列)竞争 也不会抛出异常
总结和扩展:
创建线程池使用的是ThreadPoolExecutor而不是Executros,因为Executros不安全(max=Interger.max)相当于20亿个线程,会造成OOM(内存溢出)异常
知道线程池的3种线程池(3大方法),7大参数,4种拒绝策略
线程池的最大的大小应该如何定义设置?
一般有两种方法
IO密集型和CPU密集型一般用于调优(优化性能)
1 CPU密集型 几核,就把最大线程定义为几,可以保证CPU的效率最高
Runtime.getRuntime().availableProcessors() //获取cpu处理器个数 也叫CPU密集型 //使用ThreadPoolExecutor自定义线程池 它有7个参数 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//核心线程池大小 正常只有2个线程处理 //最大线程数一般写电脑的处理器个数 使用CPU密集型 Runtime.getRuntime().availableProcessors() 获取该电脑有多少cpu处理器 Runtime.getRuntime().availableProcessors(), //最大线程池大小 并发量大的时候最多处理的线程数 3, //等待时间 3 TimeUnit.SECONDS, //时间单位 秒 new LinkedBlockingDeque<>(3),//阻塞队列 存放的队列数 Executors.defaultThreadFactory(),//线程工程 Executors的defaultThreadFactory方法默认创建的线程工厂 一般不用变 //拒绝策略 // 1 AbortPolicy 如果队列满了 还有队列进来 就不处理满了之后进来的队列,并且抛出异常 //new ThreadPoolExecutor.AbortPolicy() // 2 CallerRunsPolicy 哪来的去哪里 new ThreadPoolExecutor.CallerRunsPolicy // 3 DiscardPolicy 队列满了,丢掉多余的任务 不会抛出异常 ThreadPoolExecutor.DiscardPolicy() // 4 DiscardOldestPolicy 队列满了,会尝试和旧的队列(最早加入的队列)竞争 也不会抛出异常 new ThreadPoolExecutor.DiscardOldestPolicy() new ThreadPoolExecutor.DiscardOldestPolicy());
2 IO密集型 判断程序中十分耗费IO的线程,然后定义的最大线程大于耗费IO的线程就可以了,一般定义的最大线程数是IO耗费的两倍
比如一个程序中有15个大型任务,IO任务非常占用资源,至少有15个线程去操作IO任务,那么线程池就可以定义30个最大线程,是IO线程的两倍