线程池技术是Java的一大特性,如果我们想要编写高并发、高吞吐的程序,线程池的技术使用是必须的。对于很多程序员来说,多线程和线程池技术都了然于胸,基本原理和使用都数量掌握,分分钟可以写出一个生产消费者模式的多线程,也能写出线程集合和线程相互等待的业务功能。但是,在使用多线程和线程池技术的过程中,我们会碰到各种各样诡异的问题,这种问题会莫名其妙的出现,我们很难定位。本文就示例说明关于阻塞线程的一大坑,各位Java程序员你是否踩过?我们应该如何避免呢?

四种线程池策略

Java的JUC包中有四种内置的创建线程池的策略,借助这些方策略,我们可以很方便的创建一个线程池对象在业务中使用。这四个策略分别为:

  • 1、NewSingleThreadExecutor,单线程的线程池

单线程的线程池中只有一个线程,也就是所有任务都是通过这一个线程来串行执行,保证了执行任务按照提交顺序来执行。如果业务只是涉及到异步执行,可选择该线程池技术。

  • 2、NewFixedThreadPool,固定数量线程的线程池

固定大小的线程池创建的时候限制了线程池中线程的数量。当有新的任务创建的时候,就创建一个新的线程来执行任务。当线程池中的线程数量达到设定的值的时候,任务开始等待执行,直到有可用的线程。

  • 3、NewCachedThreadPool,可缓存的线程池

可缓存的线程池不限定线程数量,当有新的任务加入,如果没有可用的线程,那就创建新的线程。线程最大数量取决于操作系统所能创建的最大线程数量。此线程池技术也是项目开发中不被推荐的一种方法,因为如何控制不好任务数量,将会导致大量的线程被创建,影响系统的性能。

  • 4、NewScheduledThreadPool,定时执行的线程池

定时执行的线程池支持创建无线数量的线程池,并且线程按照设定的时间周期执行。

这是四种线程池策略。我们可以通过JUC的相关API来创建相应的线程池,具体需要创建什么样的线程池策略,根据业务需求。如果只是让某个业务异步执行,那就使用NewSingleThreadExecutor,如果想定义固定线程数量那就使用NewFixedThreadPool,如果是启动定时任务来执行业务逻辑,那就使用NewScheduledThreadPool。

Executors导致的”大坑“

使用这四种线程池技术看似简单,通过相应的API就能快速创建。就是因为JUC API多于线程池技术的封装,导致我们在使用线程池技术会遇到一大坑

Executors是JUC中一个核心的创建线程池的工具类,通过Executors我们可以快速的创建不同策略的线程池。Executors中的方法如下所示:

我们可以看到Executors类中提供了很多方法用来创建线程池。例如:我们要创建一个固定大小的线程池,示例代码如下:

那这段代码有没有什么问题呢?没有问题。但是,如果是在正常的业务流程中,就会有很大的问题,我们举一个实际的业务场景:

某个业务系统中,需要消费Kafka中的消息,然后将消息进行处理存库。Kafka消费消息的速度很快,但是入库的速度很慢,因为是消费和入库是同步执行的,导致Kafka的消息有挤压。所以我们需要将消费和入库分开异步执行,异步执行我们想到了使用线程池技术,我们可以开启固定数量线程的线程池技策略来多线程的入库。我们模拟一下场景代码:

通过测试发现,100次的循环很快结束,(即100条kafka消息很快消费完成)。所有的待处理任务全部加入了executorService的待处理队列任务中。如果循环1000次,executorService的队列中有大量的待处理任务,从而导致系统OOM。

通过研究源码我们可以发现,Executors.newFixedThreadPool(10)内部调用的ThreadPoolExecutor默认采用的阻塞队列是LinkedBlockingQueue,并且的它的长度是队列长度是Integer.MAX_VALUE,即2147483647。这就是导致堆积大量的请求,系统发生OOM的原因。

这就是用Executors创建线程池的一大坑,它隐藏了内部的实现细节。如果我们直接使用Executors来创建线程池,这对于高并发系统来说就是一场灾难。

ThreadPoolExecutor创建线程池

那回到上面的例子,我们如何解决这个问题呢?通过ThreadPoolExecutor直接来创建线程池,示例代码如下:

在ThreadPoolExecutor的构造方法中,我们可以定义阻塞队列的大小,这样就不会让线程池队列无限大而导致OOM了。但是,这里面又有一个坑:当阻塞队列满员,后面要再加入任务,会报错,如下错误所示:

ArrayBlockingQueue Offer方法的”坑“

通过研究源码我们发现,ThreadPoolExecutor中阻塞队列添加任务时采用的是offer方法,如下图所示:

我们都知道,LinkedBlockingQueue、ArrayBlockingQueue以及其他阻塞队列,offer方法在添加元素的时候,如果队列已满无法添加元素的时候,offer方法会直接返回false,这就会导致上面的异常

自定义阻塞队列

那如果我们要实现一个阻塞线程池队列,我们改如何实现呢?自定义阻塞队列,覆写offer方法,示例代码如下:

自定义阻塞队列:

对于put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞,这正好满足我们的需求。改造后的代码如下:

通过运行我们发现满足我们的需求。

不会堆积大量的队列任务。

总结

Java线程池技术在业务中需要慎重使用,对于线程池技术的内部实现机制我们需要精通掌握,才能在实际项目中熟练使用。文中涉及到的”坑“大家还需要好好消化理解,在以后的项目开发中尽量避免。

java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?相关推荐

  1. 线程执行完之后会释放吗_java多线程并发:CAS+AQS+HashMap+volatile+ThreadLocal,乐分享...

    CyclicBarrier.CountDownLatch.Semaphore 的用法 CountDownLatch(线程计数器 ) CountDownLatch 类位于 java.util.concu ...

  2. java主线程控制子线程_CountDownLatch控制主线程等子线程执行完--Java多线程

    1.[代码]CountDownLatch控制主线程等子线程执行完--Java多线程 package com.sihuatech.common; import java.util.concurrent. ...

  3. Java多线程面试题之如何让主线程等子线程执行完之后再执行

    问题描述 现在有一个主线程X,和两个子线程A和B,A和B之间没有依赖关系且两者的执行时间不确定,现在要求如下: 1:不限制A和B执行顺序的 2:主线程X需要在子线程A和B执行完成之后再执行 方案1 1 ...

  4. java进阶 线程池 join用法总结:thread4.join();方法,就表明thread4.join();这个线程受到贵客待遇,直到这个线程执行完,被插入这个方法的载体线程才可以执行。

    那个线程调用join 举例 thread4.join();方法,就表明thread4.join();这个线程受到贵客待遇,直到这个线程执行完,被插入这个方法的载体线程才可以执行. package ja ...

  5. Semaphore控制同时访问的线程个数countdownlatch等待多个线程执行完本身线程再执行...

    Semaphore控制同时访问的线程个数countdownlatch等待多个线程执行完本身线程再执行 Semaphore控制同时访问的线程个数countdownlatch等待多个线程执行完本身线程再执 ...

  6. java new一个线程执行完后会自动销毁吗_Java基础总结,超级全的面试题

    1. static关键字是什么意思?Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?是否可以在 static 环境中访问非static 变量? stat ...

  7. python 等待其他线程执行完_面试官:如何让线程顺序执行,join,还有其他办法吗?...

    面试官:如让线程顺序执行? 我:使用Thread的join方法. 面试官:除了join还有别的办法吗? 我:目前只用过join. 面试官:哦,那你了解CountDownLatch吗? 我:不了解,没使 ...

  8. c#中等待某个线程执行完后再执行某个线程

    在方法的外部申请一个这样的变量 CountdownEvent latch = new CountdownEvent(3); 比如现在执行的是方法a public void a() { int si=0 ...

  9. java executeupdate_执行完executeUpdate()方法…-体系课

    如题: Servlet代码如下: package jdbcHomeWork; import java.io.IOException; import java.sql.Connection; impor ...

最新文章

  1. 计算机组成原理实验pc,计算机组成原理实验报告5- PC实验
  2. LeetCode-有效的字母异位词
  3. 推荐一个好用而且免费的XML文件查看工具,高效,易用而且可定制
  4. 计算机显示有可移动存储,winxp系统中我的电脑出现很多个可移动磁盘怎么办
  5. 编写有效用例电子版_剖析用例设计方法的使用
  6. 剑指offer 答案 python_【剑指offer】【python】面试题2~5
  7. 查询数据库中字段内容相同的记录
  8. JAVA运行时异常及常见的5中RuntimeExecption
  9. 先批标准化还是先激活
  10. 18年怎么将win7升级到win10教程
  11. 网络信息安全攻防学习平台——基础关
  12. 硬盘安装ubuntu 14.04 LTS
  13. thinkphp6 循环 视图_ThinkPHP模板里怎么使用 for循环
  14. 利用LVS(Linux Virtual Server)系统实现Web服务器集群的负载均衡
  15. 保护计算机系统与数据有什么方法,计算机系统开机和硬盘数据保护方法,与其数据保护模块...
  16. VMware虚拟机从一台电脑复制到另一台电脑
  17. java8 list map相关操作汇总(不断更新~~~)
  18. 手机android player病毒怎么解决,不要担心手机中毒!教您一些有关如何彻底清除Android手机上的病毒的提示...
  19. C++中虚析构函数和纯虚函数的作用
  20. Visual Studio2013使用Microsoft Office Document Imaging(MODI)的方法

热门文章

  1. cpuz北桥频率和内存频率_内存频率不是越高越好:寻找三代锐龙的最佳频率
  2. python判断是否是完数_python判断是否完数
  3. Mysql在字符串类型的日期上加上10分钟并和如今的日期做比較
  4. android 学习过程中登陆失效的个人理解
  5. 洛谷 P3865 【模板】ST表
  6. 从0系统学 Android--1.1认识 Android
  7. strncmp用法说明
  8. vue2.0 练习项目-外卖APP(2)
  9. jmeter测试元件--控制器
  10. 【java】java开发中的23种设计模式详解