java多线程连接池_HttpClient实战二:单线程和多线程连接池实例

为什么使用HTTP连接池?

随着系统架构风格逐渐向前后端分离架构,微服务架构转变,RestFul风格API的开发与设计,同时SpringMVC也很好的支持了REST风格接口。各个系统之间服务的调用大多采用HTTP+JSON或HTTPS+JSON方式。

HTTP1.1默认是持久连接,HTTP1.0也可以通过在请求头中设置Connection:keep-alive使得连接成为长连接。既然HTTP协议支持长连接,那么HTTP连接同样可以使用连接池技术来管理和维护连接建立和销毁。 但是由于每次HTTP连接请求实际上都是在传输层建立的TCP连接,利用的socket进行通信,HTTP连接的保持和关闭实际上都同TCP连接的建立和关闭有关,所有每次HTTP请求都有经过TCP连接的三次握手(建立连接)和四次挥手(释放连接)的过程。所以采用HTTP连接池有以下优势:

降低了频繁建立HTTP连接的时间开销,减少了TCP连接建立和释放时socket通信服务器端资源的浪费;

支持更高的并发量;

常用HttpClient连接池API

本文使用的是目前最新版本的HttpClient4.5.3,所以下文的内容都是基于该版本书写。

PoolingHttpClientConnectionManager连接池管理实现类

PoolingHttpClientConnectionManager是一个HttpClient连接池实现类,实现了HttpClientConnectionManager和ConnPoolControl接口。类层次结构如下图所示:

845d67d3742ce237f49dde1b6815eb4d.png

PoolingHttpClientConnectionManager类层次结构

构造方法:

PoolingHttpClientConnectionManager():无参构造方法,从源码中可以看到该方法调用了getDefaultRegistry()来注册http协议和https协议。

常用方法:

public void setMaxTotal(int max):该方法定义在ConnPoolControl接口中,表示设置最大连接数为max。

public void setDefaultMaxPerRoute(int max):该方法也是定义在ConnPoolControl接口中,表示将每个路由的默认最大连接数设置为max

public void setMaxPerRoute(HttpRoute route,int max):设置某个指定路由的最大连接数,这个配置会覆盖setDefaultMaxPerRoute的某个路由的值。

RequestConfig请求参数配置类

689699b2a51d06d33ee660304486cee3.png

RequestConfig方法与内部类Builder

常用方法

static RequestConfig.Builder custom():静态方法,用于构建Builder 对象,然后设置相应的参数;

int getConnectionRequestTimeout():获取从连接池获取连接的最长时间,单位是毫秒;

int getConnectTimeout():获取创建连接的最长时间,单位是毫秒;

int getSocketTimeout():获取数据传输的最长时间,单位是毫秒;

RequestConfig有一个静态内部类Builder,用于构建RequestConfig对象并设置请求参数,该类有以下常用方法:

public RequestConfig.Builder setConnectionRequestTimeout(int connectionRequestTimeout):设置从连接池获取连接的最长时间,单位是毫秒;

public RequestConfig.Builder setConnectTimeout(int connectTimeout):设置创建连接的最长时间,单位是毫秒;

public RequestConfig.Builder setSocketTimeout(int socketTimeout):设置数据传输的最长时间,单位是毫秒;

HttpRequestRetryHandler请求重试接口

boolean retryRequest(IOException exception, int executionCount, org.apache.http.protocol.HttpContext context):实现该接口的,必须实现该方法,决定了如果一个方法执行时发生了IO异常,是否应该重试,重试executionCount次。

单线程-使用连接池管理HTTP请求

主要步骤:

创建HTTP的连接池管理对象cm,如下所示

1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示

1 //将最大连接数增加到200

2 cm.setMaxTotal(200);3 //将每个路由的默认最大连接数增加到20

4 cm.setDefaultMaxPerRoute(20);

3.模拟发送HttpGet请求或者HttpPost请求,注意这里创建HttpClients对象的时候需要从连接池中获取,即要设置连接池对象,当执行发生IO异常需要处理时,要实现HttpRequestRetryHandler接口;需要注意的是这里处理完请求响应后,不能关闭HttpClient对象,如果关闭连接池也会销毁。HttpClients对象创建对象所示:

1 CloseableHttpClient httpClient =HttpClients.custom()2 .setConnectionManager(connectionManager)3 .setRetryHandler(retryHandler(5)).build();

4.可以开启线程来监听连接池中空闲连接,并清理无效连接,线程监听频率可以自行设置。

Java实现源码:

1 packagecom.liangpj.develop.httpclient;2 importorg.apache.http.HttpEntityEnclosingRequest;3 importorg.apache.http.HttpRequest;4 importorg.apache.http.NoHttpResponseException;5 importorg.apache.http.client.HttpRequestRetryHandler;6 importorg.apache.http.client.config.RequestConfig;7 importorg.apache.http.client.methods.CloseableHttpResponse;8 importorg.apache.http.client.methods.HttpGet;9 importorg.apache.http.client.protocol.HttpClientContext;10 importorg.apache.http.conn.ConnectTimeoutException;11 importorg.apache.http.conn.HttpClientConnectionManager;12 importorg.apache.http.impl.client.CloseableHttpClient;13 importorg.apache.http.impl.client.HttpClients;14 importorg.apache.http.impl.conn.PoolingHttpClientConnectionManager;15 importorg.apache.http.protocol.HttpContext;16 importorg.apache.http.util.EntityUtils;17 importjavax.net.ssl.SSLException;18 importjavax.net.ssl.SSLHandshakeException;19 importjava.io.IOException;20 importjava.io.InterruptedIOException;21 importjava.net.UnknownHostException;22

23 /**

24 * 单线程-使用连接池管理HTTP请求25 *@author: liangpengju26 *@version: 1.027 */

28 public classHttpConnectManager {29

30 public static void main(String[] args) throwsException {31 //创建HTTP的连接池管理对象

32 PoolingHttpClientConnectionManager connectionManager = newPoolingHttpClientConnectionManager();33 //将最大连接数增加到200

34 connectionManager.setMaxTotal(200);35 //将每个路由的默认最大连接数增加到20

36 connectionManager.setDefaultMaxPerRoute(20);37 //将http://www.baidu.com:80的最大连接增加到5038 //HttpHost httpHost = new HttpHost("http://www.baidu.com",80);39 //connectionManager.setMaxPerRoute(new HttpRoute(httpHost),50);40

41 //发起3次GET请求

42 String url ="https://www.baidu.com/s?word=java";43 long start =System.currentTimeMillis();44 for (int i=0;i<100;i++){45 doGet(connectionManager,url);46 }47 long end =System.currentTimeMillis();48 System.out.println("consume -> " + (end -start));49

50 //清理无效连接

51 newIdleConnectionEvictor(connectionManager).start();52 }53

54 /**

55 * 请求重试处理56 *@paramtryTimes 重试次数57 *@return

58 */

59 public static HttpRequestRetryHandler retryHandler(final inttryTimes){60

61 HttpRequestRetryHandler httpRequestRetryHandler = newHttpRequestRetryHandler() {62 @Override63 public boolean retryRequest(IOException exception, intexecutionCount, HttpContext context) {64 //如果已经重试了n次,就放弃

65 if (executionCount >=tryTimes) {66 return false;67 }68 //如果服务器丢掉了连接,那么就重试

69 if (exception instanceofNoHttpResponseException) {70 return true;71 }72 //不要重试SSL握手异常

73 if (exception instanceofSSLHandshakeException) {74 return false;75 }76 //超时

77 if (exception instanceofInterruptedIOException) {78 return false;79 }80 //目标服务器不可达

81 if (exception instanceofUnknownHostException) {82 return true;83 }84 //连接被拒绝

85 if (exception instanceofConnectTimeoutException) {86 return false;87 }88 //SSL握手异常

89 if (exception instanceofSSLException) {90 return false;91 }92 HttpClientContext clientContext =HttpClientContext .adapt(context);93 HttpRequest request =clientContext.getRequest();94 //如果请求是幂等的,就再次尝试

95 if (!(request instanceofHttpEntityEnclosingRequest)) {96 return true;97 }98 return false;99 }100 };101 returnhttpRequestRetryHandler;102 }103

104 /**

105 * doGet106 *@paramurl 请求地址107 *@paramconnectionManager108 *@throwsException109 */

110 public static void doGet(HttpClientConnectionManager connectionManager,String url) throwsException {111 //从连接池中获取client对象,多例

112 CloseableHttpClient httpClient =HttpClients.custom()113 .setConnectionManager(connectionManager)114 .setRetryHandler(retryHandler(5)).build();115

116 //创建http GET请求

117 HttpGet httpGet = newHttpGet(url);118 //构建请求配置信息

119 RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) //创建连接的最长时间

120 .setConnectionRequestTimeout(500) //从连接池中获取到连接的最长时间

121 .setSocketTimeout(10 * 1000) //数据传输的最长时间10s

122 .setStaleConnectionCheckEnabled(true) //提交请求前测试连接是否可用

123 .build();124 //设置请求配置信息

125 httpGet.setConfig(config);126

127 CloseableHttpResponse response = null;128 try{129 //执行请求

130 response =httpClient.execute(httpGet);131 //判断返回状态是否为200

132 if (response.getStatusLine().getStatusCode() == 200) {133 String content = EntityUtils.toString(response.getEntity(), "UTF-8");134 System.out.println("内容长度:" +content.length());135 }136 } finally{137 if (response != null) {138 response.close();139 }140 //此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁141 //httpClient.close();

142 }143 }144

145 /**

146 * 监听连接池中空闲连接,清理无效连接147 */

148 public static class IdleConnectionEvictor extendsThread {149

150 private finalHttpClientConnectionManager connectionManager;151

152 private volatile booleanshutdown;153

154 publicIdleConnectionEvictor(HttpClientConnectionManager connectionManager) {155 this.connectionManager =connectionManager;156 }157

158 @Override159 public voidrun() {160 try{161 while (!shutdown) {162 synchronized (this) {163 //3s检查一次

164 wait(3000);165 //关闭失效的连接

166 connectionManager.closeExpiredConnections();167 }168 }169 } catch(InterruptedException ex) {170 //结束

171 ex.printStackTrace();172 }173 }174

175 public voidshutdown() {176 shutdown = true;177 synchronized (this) {178 notifyAll();179 }180 }181 }182 }

多线程-HttpClient连接池管理HTTP请求实例

主要步骤:

创建HTTP的连接池管理对象cm,如下所示

1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示

1 //将最大连接数增加到200

2 cm.setMaxTotal(200);3 //将每个路由的默认最大连接数增加到20

4 cm.setDefaultMaxPerRoute(20);

3.创建HttpClients对象的并设置连接池对象,HttpClients对象创建对象所示:

1 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();

4.继承Thread类实现一个执行Get请求线程类GetThread,重载run()方法,执行HttpGet请求;

5.定义要请求的URI地址为数组形式,为每一个URI创建一个GetThread线程,并启动所有线程;

Java实现源码:

1 packagecom.liangpj.develop.httpclient;2 importorg.apache.http.HttpEntity;3 importorg.apache.http.client.ClientProtocolException;4 importorg.apache.http.client.methods.CloseableHttpResponse;5 importorg.apache.http.client.methods.HttpGet;6 importorg.apache.http.client.protocol.HttpClientContext;7 importorg.apache.http.impl.client.CloseableHttpClient;8 importorg.apache.http.impl.client.HttpClients;9 importorg.apache.http.impl.conn.PoolingHttpClientConnectionManager;10 importorg.apache.http.protocol.HttpContext;11 importjava.io.IOException;12

13 /**

14 * 多线程-HttpClient连接池管理HTTP请求实例15 */

16 public classMultiThreadHttpConnManager {17 public static voidmain(String[] args) {18 //连接池对象

19 PoolingHttpClientConnectionManager connectionManager = newPoolingHttpClientConnectionManager();20 //将最大连接数增加到200

21 connectionManager.setMaxTotal(200);22 //将每个路由的默认最大连接数增加到20

23 connectionManager.setDefaultMaxPerRoute(20);24 //HttpClient对象

25 CloseableHttpClient httpClient =HttpClients.custom().setConnectionManager(connectionManager).build();26 //URIs to DoGet

27 String[] urisToGet ={28 "https://www.baidu.com/s?word=java",29 "https://www.baidu.com/s?word=java",30 "https://www.baidu.com/s?word=java",31 "https://www.baidu.com/s?word=java"

32 };33 //为每一个URI创建一个线程

34 GetThread[] threads = newGetThread[urisToGet.length];35 for (int i=0;i

40 for (int j=0;j

44 for(int k=0;k

53 /**

54 * 执行Get请求线程55 */

56 public static class GetThread extendsThread{57 private finalCloseableHttpClient httpClient;58 private finalHttpContext context;59 private finalHttpGet httpget;60 publicGetThread(CloseableHttpClient httpClient, HttpGet httpget) {61 this.httpClient =httpClient;62 this.context =HttpClientContext.create();63 this.httpget =httpget;64 }65 @Override66 public voidrun() {67 try{68 CloseableHttpResponse response =httpClient.execute(httpget,context);69 try{70 HttpEntity entity =response.getEntity();71 }finally{72 response.close();73 }74 }catch(ClientProtocolException ex){75 //处理客户端协议异常

76 }catch(IOException ex){77 //处理客户端IO异常

78 }79 }80 }81 }

想要获取HttpClient实战的所有实例代码,可以关注Java技术日志订阅号后,在消息框回复关键字:httpclient可以获取代码的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值