受欢迎的企业网站建设,便宜网站设计,网站建设人员培训纲要,龙岩天宫山住宿怎么订作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;根据摩尔定律所说#xff1a;集成电路上可容纳的晶体管数量每 18 个月翻一番#xff0c;因此 CPU 上的晶体管数量会越来越… 作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone根据摩尔定律所说集成电路上可容纳的晶体管数量每 18 个月翻一番因此 CPU 上的晶体管数量会越来越多。但随着时间的推移集成电路上可容纳的晶体管数量已趋向饱和摩尔定律也渐渐失效因此多核 CPU 逐渐变为主流与之相对应的多线程编程也开始变得普及和流行起来这当然也是很久之前的事了对于现在而言多线程编程已经成为程序员必备的职业技能了那接下来我们就来盘一盘“线程池”这个多线程编程中最重要的话题。什么是线程池线程池ThreadPool是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内当有任务出现时可以避免重新创建和销毁线程所带来性能开销只需要从“池子”内取出相应的线程执行对应的任务即可。池化思想在计算机的应用也比较广泛比如以下这些内存池(Memory Pooling)预先申请内存提升申请内存速度减少内存碎片。连接池(Connection Pooling)预先申请数据库连接提升申请连接的速度降低系统的开销。实例池(Object Pooling)循环使用对象减少资源在初始化和释放时的昂贵损耗。线程池的优势主要体现在以下 4 点降低资源消耗通过池化技术重复利用已创建的线程降低线程创建和销毁造成的损耗。提高响应速度任务到达时无需等待线程创建即可立即执行。提高线程的可管理性线程是稀缺资源如果无限制创建不仅会消耗系统资源还会因为线程的不合理分布导致资源调度失衡降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。提供更多更强大的功能线程池具备可拓展性允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor就允许任务延期执行或定期执行。同时阿里巴巴在其《Java开发手册》中也强制规定线程资源必须通过线程池提供不允许在应用中自行显式创建线程。说明线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销解决资源不足的问题。如果不使用线程池有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。知道了什么是线程池以及为什要用线程池之后我们再来看怎么用线程池。线程池使用线程池的创建方法总共有 7 种但总体来说可分为 2 类一类是通过 ThreadPoolExecutor 创建的线程池另一个类是通过 Executors 创建的线程池。线程池的创建方式总共包含以下 7 种其中 6 种是通过 Executors 创建的1 种是通过 ThreadPoolExecutor 创建的Executors.newFixedThreadPool创建一个固定大小的线程池可控制并发的线程数超出的线程会在队列中等待Executors.newCachedThreadPool创建一个可缓存的线程池若线程数超过处理所需缓存一段时间后会回收若线程数不够则新建线程Executors.newSingleThreadExecutor创建单个线程数的线程池它可以保证先进先出的执行顺序Executors.newScheduledThreadPool创建一个可以执行延迟任务的线程池Executors.newSingleThreadScheduledExecutor创建一个单线程的可以执行延迟任务的线程池Executors.newWorkStealingPool创建一个抢占式执行的线程池任务执行顺序不确定【JDK 1.8 添加】。ThreadPoolExecutor最原始的创建线程池的方式它包含了 7 个参数可供设置后面会详细讲。单线程池的意义从以上代码可以看出 newSingleThreadExecutor 和 newSingleThreadScheduledExecutor 创建的都是单线程池那么单线程池的意义是什么呢答虽然是单线程池但提供了工作队列生命周期管理工作线程维护等功能。那接下来我们来看每种线程池创建的具体使用。1.FixedThreadPool创建一个固定大小的线程池可控制并发的线程数超出的线程会在队列中等待。使用示例如下public static void fixedThreadPool() {// 创建 2 个数据级的线程池ExecutorService threadPool Executors.newFixedThreadPool(2);// 创建任务Runnable runnable new Runnable() {Overridepublic void run() {System.out.println(任务被执行,线程: Thread.currentThread().getName());}};// 线程池执行任务(一次添加 4 个任务)// 执行任务的方法有两种:submit 和 executethreadPool.submit(runnable); // 执行方式 1:submitthreadPool.execute(runnable); // 执行方式 2:executethreadPool.execute(runnable);threadPool.execute(runnable);
}
执行结果如下如果觉得以上方法比较繁琐还用更简单的使用方法如下代码所示public static void fixedThreadPool() {// 创建线程池ExecutorService threadPool Executors.newFixedThreadPool(2);// 执行任务threadPool.execute(() - {System.out.println(任务被执行,线程: Thread.currentThread().getName());});
}
2.CachedThreadPool创建一个可缓存的线程池若线程数超过处理所需缓存一段时间后会回收若线程数不够则新建线程。使用示例如下public static void cachedThreadPool() {// 创建线程池ExecutorService threadPool Executors.newCachedThreadPool();// 执行任务for (int i 0; i 10; i) {threadPool.execute(() - {System.out.println(任务被执行,线程: Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}});}
}
执行结果如下从上述结果可以看出线程池创建了 10 个线程来执行相应的任务。3.SingleThreadExecutor创建单个线程数的线程池它可以保证先进先出的执行顺序。使用示例如下public static void singleThreadExecutor() {// 创建线程池ExecutorService threadPool Executors.newSingleThreadExecutor();// 执行任务for (int i 0; i 10; i) {final int index i;threadPool.execute(() - {System.out.println(index :任务被执行);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}});}
}
执行结果如下4.ScheduledThreadPool创建一个可以执行延迟任务的线程池。使用示例如下public static void scheduledThreadPool() {// 创建线程池ScheduledExecutorService threadPool Executors.newScheduledThreadPool(5);// 添加定时执行任务(1s 后执行)System.out.println(添加任务,时间: new Date());threadPool.schedule(() - {System.out.println(任务被执行,时间: new Date());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}}, 1, TimeUnit.SECONDS);
}
执行结果如下从上述结果可以看出任务在 1 秒之后被执行了符合我们的预期。5.SingleThreadScheduledExecutor创建一个单线程的可以执行延迟任务的线程池。使用示例如下public static void SingleThreadScheduledExecutor() {// 创建线程池ScheduledExecutorService threadPool Executors.newSingleThreadScheduledExecutor();// 添加定时执行任务(2s 后执行)System.out.println(添加任务,时间: new Date());threadPool.schedule(() - {System.out.println(任务被执行,时间: new Date());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}}, 2, TimeUnit.SECONDS);
}
执行结果如下从上述结果可以看出任务在 2 秒之后被执行了符合我们的预期。6.newWorkStealingPool创建一个抢占式执行的线程池任务执行顺序不确定注意此方法只有在 JDK 1.8 版本中才能使用。使用示例如下public static void workStealingPool() {// 创建线程池ExecutorService threadPool Executors.newWorkStealingPool();// 执行任务for (int i 0; i 10; i) {final int index i;threadPool.execute(() - {System.out.println(index 被执行,线程名: Thread.currentThread().getName());});}// 确保任务执行完成while (!threadPool.isTerminated()) {}
}
执行结果如下从上述结果可以看出任务的执行顺序是不确定的因为它是抢占式执行的。7.ThreadPoolExecutor最原始的创建线程池的方式它包含了 7 个参数可供设置。使用示例如下public static void myThreadPoolExecutor() {// 创建线程池ThreadPoolExecutor threadPool new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue(10));// 执行任务for (int i 0; i 10; i) {final int index i;threadPool.execute(() - {System.out.println(index 被执行,线程名: Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}
}
执行结果如下ThreadPoolExecutor 参数介绍ThreadPoolExecutor 最多可以设置 7 个参数如下代码所示 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 省略...}
7 个参数代表的含义如下参数 1corePoolSize核心线程数线程池中始终存活的线程数。参数 2maximumPoolSize最大线程数线程池中允许的最大线程数当线程池的任务队列满了之后可以创建的最大线程数。参数 3keepAliveTime最大线程数可以存活的时间当线程中没有任务执行时最大线程就会销毁一部分最终保持核心线程数量的线程。参数 4unit:单位是和参数 3 存活时间配合使用的合在一起用于设定线程的存活时间 参数 keepAliveTime 的时间单位有以下 7 种可选TimeUnit.DAYS天TimeUnit.HOURS小时TimeUnit.MINUTES分TimeUnit.SECONDS秒TimeUnit.MILLISECONDS毫秒TimeUnit.MICROSECONDS微妙TimeUnit.NANOSECONDS纳秒参数 5workQueue一个阻塞队列用来存储线程池等待执行的任务均为线程安全它包含以下 7 种类型ArrayBlockingQueue一个由数组结构组成的有界阻塞队列。LinkedBlockingQueue一个由链表结构组成的有界阻塞队列。SynchronousQueue一个不存储元素的阻塞队列即直接提交给线程不保持它们。PriorityBlockingQueue一个支持优先级排序的无界阻塞队列。DelayQueue一个使用优先级队列实现的无界阻塞队列只有在延迟期满时才能从中提取元素。LinkedTransferQueue一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似还含有非阻塞方法。LinkedBlockingDeque一个由链表结构组成的双向阻塞队列。较常用的是 LinkedBlockingQueue 和 Synchronous线程池的排队策略与 BlockingQueue 有关。参数 6threadFactory线程工厂主要用来创建线程默认为正常优先级、非守护线程。参数 7handler拒绝策略拒绝处理任务时的策略系统提供了 4 种可选AbortPolicy拒绝并抛出异常。CallerRunsPolicy使用当前调用的线程来执行此任务。DiscardOldestPolicy抛弃队列头部最旧的一个任务并执行当前任务。DiscardPolicy忽略并抛弃当前任务。默认策略为 AbortPolicy。线程池的执行流程ThreadPoolExecutor 关键节点的执行流程如下当线程数小于核心线程数时创建线程。当线程数大于等于核心线程数且任务队列未满时将任务放入任务队列。当线程数大于等于核心线程数且任务队列已满若线程数小于最大线程数创建线程若线程数等于最大线程数抛出异常拒绝任务。线程池的执行流程如下图所示线程拒绝策略我们来演示一下 ThreadPoolExecutor 的拒绝策略的触发我们使用 DiscardPolicy 的拒绝策略它会忽略并抛弃当前任务的策略实现代码如下public static void main(String[] args) {// 任务的具体方法Runnable runnable new Runnable() {Overridepublic void run() {System.out.println(当前任务被执行,执行时间: new Date() 执行线程: Thread.currentThread().getName());try {// 等待 1sTimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}};// 创建线程,线程的任务队列的长度为 1ThreadPoolExecutor threadPool new ThreadPoolExecutor(1, 1,100, TimeUnit.SECONDS, new LinkedBlockingQueue(1),new ThreadPoolExecutor.DiscardPolicy());// 添加并执行 4 个任务threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);
}
我们创建了一个核心线程数和最大线程数都为 1 的线程池并且给线程池的任务队列设置为 1这样当我们有 2 个以上的任务时就会触发拒绝策略执行的结果如下图所示从上述结果可以看出只有两个任务被正确执行了其他多余的任务就被舍弃并忽略了。其他拒绝策略的使用类似这里就不一一赘述了。自定义拒绝策略除了 Java 自身提供的 4 种拒绝策略之外我们也可以自定义拒绝策略示例代码如下public static void main(String[] args) {// 任务的具体方法Runnable runnable new Runnable() {Overridepublic void run() {System.out.println(当前任务被执行,执行时间: new Date() 执行线程: Thread.currentThread().getName());try {// 等待 1sTimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}};// 创建线程,线程的任务队列的长度为 1ThreadPoolExecutor threadPool new ThreadPoolExecutor(1, 1,100, TimeUnit.SECONDS, new LinkedBlockingQueue(1),new RejectedExecutionHandler() {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 执行自定义拒绝策略的相关操作System.out.println(我是自定义拒绝策略~);}});// 添加并执行 4 个任务threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);
}
程序的执行结果如下究竟选用哪种线程池经过以上的学习我们对整个线程池也有了一定的认识了那究竟该如何选择线程池呢我们来看下阿里巴巴《Java开发手册》给我们的答案【强制】线程池不允许使用 Executors 去创建而是通过 ThreadPoolExecutor 的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险。说明Executors 返回的线程池对象的弊端如下1 FixedThreadPool 和 SingleThreadPool允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致 OOM。2CachedThreadPool允许的创建线程数量为 Integer.MAX_VALUE可能会创建大量的线程从而导致 OOM。所以综上情况所述我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建因为这种创建方式更可控并且更加明确了线程池的运行规则可以规避一些未知的风险。总结本文我们介绍了线程池的 7 种创建方式其中最推荐使用的是 ThreadPoolExecutor 的方式进行线程池的创建ThreadPoolExecutor 最多可以设置 7 个参数当然设置 5 个参数也可以正常使用ThreadPoolExecutor 当任务过多处理不过来时提供了 4 种拒绝策略当然我们也可以自定义拒绝策略希望本文的内容能帮助到你。原创不易觉得不错就点个赞再走吧参考 鸣谢https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.htmlhttps://www.cnblogs.com/pcheng/p/13540619.html
往期推荐
求求你别再用wait和notify了2020年终总结新的“开始”提高生产力最全 MyBatisPlus 讲解关注我每天陪你进步一点点