java线程池

发布时间 2023-08-25 13:17:25作者: 邢帅杰

Executors创建线程的4种方法
//1. newSingleThreadExecutor创建“单线程化线程池”
//特点:
//单线程化的线程池中的任务是按照提交的次序顺序执行的。
//只有一个线程的线程池。
//池中的唯一线程的存活时间是无限的。
//当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的。
//适用场景:任务按照提交次序,一个任务一个任务地逐个执行的场景。
// ExecutorService pool1 = Executors.newSingleThreadExecutor();
// for (int i = 0; i < 3; i++) {
// pool1.execute(new TargetTask());// 无返回值,主线程捕捉不到子线程的异常。
// Future future = pool1.submit(new TargetTask());// 有返回值
// // get()会阻塞线程。只有在调用get()的时候主线程才能捕捉到子线程异常。
// //Object o = future.get();
// }
// pool1.shutdown();

//2. newFixedThreadPool创建“固定数量的线程池”
//特点:
//如果线程数没有达到“固定数量”,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定的数量。
//线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
//在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)。
//适用场景:
//需要任务长期执行的场景,CPU密集型任务。
//缺点:内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使服务器资源迅速耗尽。
// ExecutorService pool2 = Executors.newFixedThreadPool(3);//创建含有3个线程的线程池
// for (int i = 0; i < 5; i++) {
// pool2.execute(new TargetTask());
// pool2.submit(new TargetTask());
// }
// pool2.shutdown();

//3. newCachedThreadPool创建“可缓存线程池”
//特点:
//在接收新的异步任务target执行目标实例时,如果池内所有线程繁忙,此线程池就会添加新线程来处理任务。
//线程池不会对线程池大小进行限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
//如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,就会回收空闲(60秒不执行任务)线程。
//适用场景:
//需要快速处理突发性强、耗时较短的任务场景,如Netty的NIO处理场景、REST API接口的瞬时削峰场景。
//缺点:
//线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能会因创建线程过多而导致资源耗尽。
// ExecutorService pool3 = Executors.newCachedThreadPool();
// for (int i = 0; i < 5; i++) {
// pool3.execute(new TargetTask());
// pool3.submit(new TargetTask());
// }
// pool3.shutdown();

//4. newScheduledThreadPool创建“可调度线程池”
//延时性,周期性,主要问题在于线程数不设上限
//总结:Executors创建线程池的4种方法十分方便,但是构造器创建普通线程池、可调度线程池比较复杂,
//这些构造器会涉及大量的复杂参数,已经较少使用。
// ScheduledExecutorService pool4 = Executors.newScheduledThreadPool(1);
// for (int i = 0; i < 5; i++) {
// //pool.scheduleAtFixedRate(new TargetTask1(), 0, 500, TimeUnit.MILLISECONDS);
// //参数1: task任务
// //参数2: 首次执行任务的延迟时间
// //参数3: 周期性执行的时间
// //参数4: 时间单位
// pool4.scheduleAtFixedRate(new TargetTask(), 0, 1, TimeUnit.SECONDS);
// }
// //主线程睡眠时间越长 周期次数越多
// try {
// Thread.sleep(10000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// pool4.shutdown();//线程池停止,不再执行任务。

//5.线程池标准用法 ThreadPoolExecutor
//5.1. 核心线程和最大线程数量
//corePoolSize用于设置核心(Core)线程池数量,参数maximumPoolSize用于设置最大线程数量
//线程池接收到新任务,当前工作线程数少于corePoolSize, 即使有空闲的工作线程,也会创建新的线程来处理该请求,直到线程数达到corePoolSize
//当前工作线程数多于corePoolSize数量,但小于maximumPoolSize数量,那么仅当任务排队队列已满时才会创建新线程
//maximumPoolSize被设置为无界值(如Integer.MAX_VALUE)时,线程池可以接收任意数量的并发任务
//5.2. BlockingQueue
//BlockingQueue(阻塞队列)的实例用于暂存接收到的异步任务,如果线程池的核心线程都在忙,那么所接收到的目标任务缓存在阻塞队列中。
//特点:在一个线程从一个空的阻塞队列中获取元素时线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒。
//常见的几种阻塞队列的实现:
//ArrayBlockingQueue:是一个数组实现的有界阻塞队列(有界队列),队列中的元素按FIFO排序,ArrayBlockingQueue在创建时必须设置大小
//LinkedBlockingQueue:是一个基于链表实现的阻塞队列,按FIFO排序任务,可以设置容量(有界队列),不设置容量则默认使用Integer.Max_VALUE作为容量(无界队列)
//PriorityBlockingQueue:是具有优先级的无界队列
//5.3. keepAliveTime
//空闲线程存活时间
//用于设置池内线程最大Idle(空闲)时长(或者说保活时长)
//超过这个时间,默认情况下Idle、非Core线程会被回收
//注意:若调用了allowCoreThreadTimeOut(boolean)方法,并且传入了参数true,则keepAliveTime参数所设置的Idle超时策略也将被应用于核心线程

//6. 向线程池提交任务的两种方式
//execute方法
//void execute(Runnable command): Executor接口中的方法
//submit方法
//<T> Future<T> submit(Callable<T> task);
//<T> Future<T> submit(Runnable task, T result);
//Future<?> submit(Runnable task);
//这3个submit方法都是ExecutorService接口中的方法
//两种方法的区别:
//execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable、Runnable两种类型的参数
//Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果
//submit()提交任务后会有返回值,而execute()没有
//submit()方便Exception处理

//7. 线程池的任务调度流程
//如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程
//如果线程池中总的任务数量大于核心线程池数量,新接收的任务将被加入阻塞队列中,一直到阻塞队列已满。
//当完成一个任务的执行时,执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空
//在核心线程池数量已经用完、阻塞队列也已经满了的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务
//在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出maximumPoolSize。如果线程池的线程总数超过maximumPoolSize,线程池就会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略
//注意点:
//核心和最大线程数量、BlockingQueue队列等参数如果配置得不合理,可能会造成异步任务得不到预期的并发执行,造成严重的排队等待现象
//线程池的调度器创建线程的一条重要的规则是:在corePoolSize已满之后,还需要等阻塞队列已满,才会去创建新的线程
//example: 设置核心线程数量为1,阻塞队列为100,有5个任务待执行(假设极端情况下任务一直执行不接受),则只有1个任务可以被执行,其他4个任务在阻塞队列中,而不是创建新线程进行处理(阻塞队列未满)
// ExecutorService pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2)) {
// @Override
// protected void terminated() {
// System.out.println(Thread.currentThread().getId() + "调度器已停止...");
// }
//
// @Override
// protected void beforeExecute(Thread t, Runnable target) {
// System.out.println(Thread.currentThread().getId() + "前钩执行...");
// super.beforeExecute(t, target);
// }
//
// @Override
// protected void afterExecute(Runnable target, Throwable t) {
// System.out.println(Thread.currentThread().getId() + "后钩执行...");
// super.afterExecute(target, t);
// }
// };
// for (int i = 0; i < 5; i++) {
// pool.execute(new TargetTask1());
// }
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// pool.shutdown();

//8. ThreadFactory(线程工厂)
// ThreadFactory是Java线程工厂接口,只有1个方法,调用ThreadFactory的唯一方法newThread()创建新线程时,
// 可以更改所创建的新线程的名称、线程组、优先级、守护进程状态等
// 使用Executors创建新的线程池时,可以指定工厂,未指定是默认使用线程池时,
// 也可以基于ThreadFactory(线程工厂)创建,在创建新线程池时可
// ExecutorService pool = Executors.newFixedThreadPool(2, new SimpleThreadFactory());
// for (int i = 0; i < 5; i++) {
// pool.submit(new TargetTask1());
// }
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// pool.shutdown();
来源:https://blog.csdn.net/qq_43478694/article/details/123385279