你好!我是miniluo,今天和你分享使用HttpClient过程中,未考虑释放连接和并发导致的坑。

HttpClient在项目中还是比较常见的,主要都是通过GET或POST请求第三方以获取响应结果。前段时间还了解到也有企业用它来做爬虫。下面我们就从两方面来一起学习HttpClient。

强占着不放

我们先来看一张因未释放连接导致的异常图。

我们再来看代码,代码很简单,用一个static修饰HttpClient的一个对象,也是用static的方式实例化(并发下,static实例化的对象是线程不安全的)。

1@Slf4j

2public class MyHttpClientTest{

3    private static HttpClient client;

4    static {

5        RequestConfig requestConfig = RequestConfig.custom()

6                .setConnectTimeout(5000)

7                .setConnectionRequestTimeout(3000)

8                .setSocketTimeout(5000).build();

9        client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)

10                .build();

11    }

12

13    @Test

14    public void testHttpClient(){

15        for (int i = 0; i

16            String url = "http://127.0.0.1:9092/learn/product/get/12";

17            HttpGet httpGet = new HttpGet(url);

18            try {

19                HttpResponse res = client.execute(httpGet);

20            } catch (IOException e) {

21                log.error("异常: ",e);

22                return;

23            }

24        }

25    }

26}

我们看回异常图,图中有2个红框,我们先来看第一个红框的“[total kept alive: 0; route allocated: 2 of2; total allocated: 2 of 20]”,这个DEBUG级别日志描述的什么意思呢?也就是说这个route下共有2个连接,已用2个;pool共有20个,已用2个。再来看第二个红框“Timeout waiting for connection from pool”,从连接池获取连接等待超时。奇怪吧,这顺序执行也会出现?这其实是前两个连接响应结果回来后,并没有释放连接资源,导致后面的请求等待超时。

我们看看PoolingHttpClientConnectionManager类的构造函数,其给Pool初始化时给maxConnPerRoute和maxConnTotal设置了默认值。

1    public PoolingHttpClientConnectionManager(

2        final HttpClientConnectionOperator httpClientConnectionOperator,

3        final HttpConnectionFactory connFactory,

4        final long timeToLive, final TimeUnit tunit){

5        super();

6        this.configData = new ConfigData();

7        //defaultMaxPerRoute默认为2,maxTotal默认为20

8        this.pool = new CPool(new InternalConnectionFactory(

9                this.configData, connFactory), 2, 20, timeToLive, tunit);

10        this.pool.setValidateAfterInactivity(2000);

11        this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");

12        this.isShutDown = new AtomicBoolean(false);

13    }

所以当请求连接被占用2个后,后面的请求并不会获取到连接,这么说,我们需要释放连接资源。这里我们需要介绍EntityUtils这个类,里面包含了两个释放连接资源的方法consumeQuietly(HttpEntity entity)和consume(HttpEntity entity),前者内部也是请求后者完成关闭InputStream。调整后的代码如下:

1@Test

2    public void testHttpClient(){

3        for (int i = 0; i

4            String url = "http://127.0.0.1:9092/learn/product/get/12";

5            HttpGet httpGet = new HttpGet(url);

6            HttpResponse res = null;

7            try {

8                res = client.execute(httpGet);

9            } catch (IOException e) {

10                log.error("异常: ", e);

11                return;

12            } finally {

13                if (null != res) {

14                    EntityUtils.consumeQuietly(res.getEntity());

15                }

16            }

17        }

18    }

调整完代码后,我们执行后发现5个请求都能正确请求并得到响应结果。或许有同学会问到如果我调整maxConnPerRoute和maxConnTotal不也行吗?可以,但是你的连接还是没有释放,当超过你设置值后也会出现无法获得连接池的问题。

被CloseableHttpClient名字所坑

最近项目中就是使用到CloseableHttpClient实现请求第三方,踩完上面的坑后,我们在项目中写了一个工厂类内部用枚举方式实现单例。经过并发测试后,的确没有出现上述坑,看了CloseableHttpClient有一个execute方法,最后也有释放资源,所以没有在意。直到今天写文章对应的测试案例我才发现我被这个方法欺骗了。

CloseableHttpClient的execute()重载了多个方法,而实际调用能释放资源的execute必须是包含ResponseHandler extends T>这个属性,否则和原有HttpClient方式是一样的。为何,模拟高并发下并没有发生问题呢?这是因为项目中请求响应结果后是用下面的代码完成数据获取的。

1 HttpResponse response = client.execute(get);

2 String res = EntityUtils.toString(response.getEntity());

跟进EntityUtils.toString()方法的最底层调用,会执行InputStream关闭流,和上文说的consume方法一样。但是这里坑就坑在,如果请求第三方超时或其他异常,则直接跳过,并没有执行上面的toString()方法,那就不会释放连接资源。

知道坑所在,要解决也就好办。可以有下述两个方案:

1、外层调用增加finally释放连接,上文所述。

2、实例化一个ResponseHandler对象,调用实现了释放连接资源的方法。

总结

今天我们一起学习了HttpClient和CloseableHttpClient没有正确释放连接,以及并发受限的问题。实际项目中如果没有考虑到这两个情况,则会带来生产问题,后面的请求一直无法获取到连接资源,以及并发量起不来。很多项目并不会有这个问题是因为HttpClient和CloseableHttpClient并不是单例的,每次使用都会是新的实例。希望通过今天的学习对你日后使用HttpClient更加得心应手。由于篇幅问题,没有把CloseableHttpClient案例放上来,有兴趣的同学可以到GitHub上下载(https://github.com/littleluo/bj-share-java.git)。

思考和讨论

1、案例中,我们使用了static和枚举的方式解决单例问题,除了这两种方法外,还有哪些方式呢?他们各自的优缺点是什么?

2、说回前面关于分布式锁的文章中,我们用到了redis,也提到client.close()方式归还连接,为何是归还连接,而不是关闭连接呢?

欢迎留言与我分享和指正!也欢迎你把这篇文章分享给你的朋友或同事,一起交流。

感谢您的阅读,我们下节再见!

扫码关注我们,与君共进

java httpclient 关闭_【Java系列007】HttpClient调用:你考虑过关闭连接、并发了吗?...相关推荐

  1. java取负数_[Java] 告别“CV 工程师”码出高效!(基础篇)

    作为一名资深的 CV 工程师,某天,当我再一次日常看见满屏的报错信息与键盘上已经磨的泛白的 Ctrl.C.V 这三个按键时,我顿悟了. 百度谷歌复制粘贴虽然很香,但是总是依靠前人种树,终会有一天失去乘 ...

  2. 断言java怎么用_[java] java断言的使用

    所谓断言(assertion)是一个Java语句,布尔表达式,程序员认为在程序执行时该表达式的值应该为true.系统通过计算该布尔表达式执行断言,若该表达式为false系统会报告一个错误. 1.断言是 ...

  3. java星空屏幕_[Java教程]窗口设置_星空网

    窗口设置 2016-04-13 0 /** * 这个是GUI的事例程序: * */ package w160412.wang.main;import java.awt.Color; import ja ...

  4. java控制语句练习题_[Java初探实例篇02]__流程控制语句知识相关的实例练习

    本例就流程控制语句的应用方面,通过三个练习题来深入学习和巩固下学习的流程控制语句方面的知识,设计到,if条件判断语句,switch多分支语句,for循环语句及其嵌套多层使用,while循环语句. 练习 ...

  5. java 文本压缩_[Java基础]Java使用GZIP进行文本压缩

    import java.io.IOException; import java.util.zip.GZIPOutputStream; import org.apache.commons.io.outp ...

  6. java private 对象_[Java笔记]类的所有构造器都是private权限,就一定没有办法实例化它的对象了么?...

    笔者以前学过C++语言.众所周知,C++也是一门面向对象程序设计语言.还记得当时在大学的时候,老师讲过这样的话:类的构造函数不应该设置成private权限,这样的话还怎么去实例化类的对象?当时也信以为 ...

  7. java resources 目录_[Java] 在 jar 文件中读取 resources 目录下的文件

    注意两点: 1. 将资源目录添加到 build path,确保该目录下的文件被拷贝到 jar 文件中. 2. jar 内部的东西,可以当作 stream 来读取,但不应该当作 file 来读取. 例子 ...

  8. java斗地主发牌_[Java源码]扑克牌——斗地主发牌实现

    --------------------------------------- --------------------------------------- ----------一个扑克牌核心和简单 ...

  9. java小朋友猜拳_[Java教程]Java猜拳小游戏(剪刀、石头、布)

    [Java教程]Java猜拳小游戏(剪刀.石头.布) 0 2015-09-29 08:00:04 import java.util.Random;import java.util.Scanner;pu ...

  10. java 不退出_(Java)如果我在其外面点击,JPopupMenu将不会关闭

    我创建了一个 Java Swing应用程序,该应用程序没有可见的主窗口,但通过右键单击可通过其托盘图标进行控制. 我正在使用JPopupMenu,但是当我点击弹出菜单之外(例如在另一个应用程序的窗口或 ...

最新文章

  1. 基于alipay用到的
  2. Vue.js 自定义指令
  3. struts2常见错误及解决总结
  4. 基于若依框架的二次开发_SAP Commerce(原Hybris)的订单处理框架和SAP CRM One Order框架...
  5. Spring 中获取servletContext及WebApplicationContext以及applicationContext三者之间的关系
  6. Java黑皮书课后题第5章:*5.15(显示ASCII码字符表)编写一个程序,打印ASCII字符表从‘!‘到‘~‘的字符。每行打印10个字符。ASCII码表如附录B所示。数字之间用一个空格字符隔开
  7. 41. First Missing Positive 缺失的第一个正数
  8. python gridsearch_python gridsearch中的内存错误
  9. 如何在CentOS 7上安装和使用PostgreSQL
  10. kube-scheduler 源码解析
  11. Android 布局之GridLayout(转载)
  12. 利用Python Matplotlib库做简单的视觉化(2)
  13. 第六章-深入理解类(一)
  14. php 判断字符串类型
  15. KST1G SD卡脚本提取JPG
  16. 舞台音效控制软件_QLab Pro 4.3.2 优秀的舞台音乐控制软件
  17. 求100以内的素数,全部打印出来
  18. vs code设置默认浏览器
  19. 如何在InfoPath2007中接受URL参数
  20. 报SQL异常Parameter index out of range (1 > number of parameters, which is 0).

热门文章

  1. 影响 oracle 性能的常见事件
  2. React Native 实现物流进度信息
  3. mysql实现decode_Oracle中的DECODE()函数,MySQL中怎么实现DECODE()函数
  4. consul作为服务注册中心
  5. Map接口的实现类HashMap的操作
  6. printf函数源码linux,再来一版简易的printf函数实现
  7. strcpy、memcpy和memset的区别
  8. python 中调用shell命令
  9. Handler post用法整理
  10. 关于C#程序调用AMFPHP服务的问题!!