使用java的HttpClient实现多线程并发
说明:以下的代码基于httpclient4.5.2实现。
我们要使用java的HttpClient实现get请求抓取网页是一件比较容易实现的工作:
publicstaticStringget(Stringurl){
CloseableHttpResponseresponse=null;
BufferedReaderin=null;
Stringresult="";
try{
CloseableHttpClienthttpclient=HttpClients.createDefault();
HttpGethttpGet=newHttpGet(url);
response=httpclient.execute(httpGet);
in=newBufferedReader(newInputStreamReader(response.getEntity().getContent()));
StringBuffersb=newStringBuffer("");
Stringline="";
StringNL=System.getProperty("line.separator");
while((line=in.readLine())!=null){
sb.append(line+NL);
}
in.close();
result=sb.toString();
}catch(IOExceptione){
e.printStackTrace();
}finally{
try{
if(null!=response)response.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
returnresult;
}
要多线程执行get请求时上面的方法也堪用。不过这种多线程请求是基于在每次调用get方法时创建一个HttpClient实例实现的。每个HttpClient实例使用一次即被回收。这显然不是一种最优的实现。
HttpClient提供了多线程请求方案,可以查看官方文档的《Poolingconnectionmanager》这一节。HttpCLient实现多线程请求是基于内置的连接池实现的,其中有一个关键的类即PoolingHttpClientConnectionManager,这个类负责管理HttpClient连接池。在PoolingHttpClientConnectionManager中提供了两个关键的方法:setMaxTotal和setDefaultMaxPerRoute。setMaxTotal设置连接池的最大连接数,setDefaultMaxPerRoute设置每个路由上的默认连接个数。此外还有一个方法setMaxPerRoute——单独为某个站点设置最大连接个数,像这样:
HttpHosthost=newHttpHost("locahost",80);
cm.setMaxPerRoute(newHttpRoute(host),50);
根据文档稍稍调整下我们的get请求实现:
packagecom.zhyea.robin;
importorg.apache.http.client.methods.CloseableHttpResponse;
importorg.apache.http.client.methods.HttpGet;
importorg.apache.http.impl.client.CloseableHttpClient;
importorg.apache.http.impl.client.HttpClients;
importorg.apache.http.impl.conn.PoolingHttpClientConnectionManager;
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStreamReader;
publicclassHttpUtil{
privatestaticCloseableHttpClienthttpClient;
static{
PoolingHttpClientConnectionManagercm=newPoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
cm.setDefaultMaxPerRoute(50);
httpClient=HttpClients.custom().setConnectionManager(cm).build();
}
publicstaticStringget(Stringurl){
CloseableHttpResponseresponse=null;
BufferedReaderin=null;
Stringresult="";
try{
HttpGethttpGet=newHttpGet(url);
response=httpClient.execute(httpGet);
in=newBufferedReader(newInputStreamReader(response.getEntity().getContent()));
StringBuffersb=newStringBuffer("");
Stringline="";
StringNL=System.getProperty("line.separator");
while((line=in.readLine())!=null){
sb.append(line+NL);
}
in.close();
result=sb.toString();
}catch(IOExceptione){
e.printStackTrace();
}finally{
try{
if(null!=response)response.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
returnresult;
}
publicstaticvoidmain(String[]args){
System.out.println(get("https://www.baidu.com/"));
}
}
这样就差不多了。不过对于我自己而言,我更喜欢httpclient的fluent实现,比如我们刚才实现的httpget请求完全可以这样简单的实现:
packagecom.zhyea.robin;
importorg.apache.http.client.fluent.Request;
importjava.io.IOException;
publicclassHttpUtil{
publicstaticStringget(Stringurl){
Stringresult="";
try{
result=Request.Get(url)
.connectTimeout(1000)
.socketTimeout(1000)
.execute().returnContent().asString();
}catch(IOExceptione){
e.printStackTrace();
}
returnresult;
}
publicstaticvoidmain(String[]args){
System.out.println(get("https://www.baidu.com/"));
}
}
我们要做的只是将以前的httpclient依赖替换为fluent-hc依赖:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.2</version> </dependency>
并且这个fluent实现天然就是采用PoolingHttpClientConnectionManager完成的。它设置的maxTotal和defaultMaxPerRoute的值分别是200和100:
CONNMGR=newPoolingHttpClientConnectionManager(sfr); CONNMGR.setDefaultMaxPerRoute(100); CONNMGR.setMaxTotal(200);
唯一一点让人不爽的就是Executor没有提供调整这两个值的方法。不过这也完全够用了,实在不行的话,还可以考虑重写Executor方法,然后直接使用Executor执行get请求:
Executor.newInstance().execute(Request.Get(url)) .returnContent().asString();
就这样!