有一段时间,我一直在多线程环境中使用HttpClient。对于每个线程,当它发起连接时,都会创建一个全新的 HttpClient 实例。
最近我发现,使用这种方式会导致用户打开的端口过多,并且大部分连接处于TIME_WAIT状态。
http://www.opensubscriber.com/message/[电子邮件受保护]/86045.html
因此,不是每个线程都这样做:
HttpClient c = new HttpClient();
try {
c.executeMethod(method);
}
catch(...) {
}
finally {
method.releaseConnection();
}
我们计划:
[方法A]
// global_c is initialized once through
// HttpClient global_c = new HttpClient(new MultiThreadedHttpConnectionManager());
try {
global_c.executeMethod(method);
}
catch(...) {
}
finally {
method.releaseConnection();
}
正常情况下,global_c会被50++个线程同时访问。我想知道,这会产生任何性能问题吗? MultiThreadedHttpConnectionManager是否使用无锁机制来实现其线程安全策略?
如果有10个线程正在使用global_c,其他40个线程会被锁定吗?
或者如果我在每个线程中创建一个 HttpClient 实例,但显式释放连接管理器会更好吗?
[方法B]
MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManager();
HttpClient c = new HttpClient(connman);
try {
c.executeMethod(method);
}
catch(...) {
}
finally {
method.releaseConnection();
connman.shutdown();
}
connman.shutdown() 会遇到性能问题吗?
对于使用 50++ 线程的应用程序,我可以知道哪种方法(A 或 B)更好?
肯定是方法 A,因为它是池化的且线程安全的。
如果您使用的是 httpclient 4.x,连接管理器称为 ThreadSafeClientConnManager。请参阅此链接了解更多详细信息(向下滚动到“池连接管理器”)。例如:
HttpParams params = new BasicHttpParams();
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, registry);
HttpClient client = new DefaultHttpClient(cm, params);
方法A是httpclient开发者社区推荐的。
请参阅 http://www.mail-archive.com/[email protected]/msg02455.html 了解更多详情。
我对文档的阅读是,HttpConnection 本身不被视为线程安全,因此 MultiThreadedHttpConnectionManager 提供了一个可重用的 HttpConnection 池,您有一个由所有线程共享的 MultiThreadedHttpConnectionManager 并初始化一次。所以你需要对选项 A 进行一些小的改进。
MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManag
然后每个线程应该为每个请求使用该序列,从池中获取连接并在完成工作后将其放回 - 使用finally块可能会很好。 您还应该针对池没有可用连接的可能性进行编码并处理超时异常。
HttpConnection connection = null
try {
connection = connman.getConnectionWithTimeout(
HostConfiguration hostConfiguration, long timeout)
// work
} catch (/*etc*/) {/*etc*/} finally{
if ( connection != null )
connman.releaseConnection(connection);
}
当您使用连接池时,您实际上不会关闭连接,因此这不应该遇到 TIME_WAIT 问题。这种方法确实假设每个线程不会长时间挂起连接。请注意,conman 本身保持打开状态。
使用 HttpClient 4.5,您可以执行以下操作:
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(new PoolingHttpClientConnectionManager()).build();
请注意,这个实现了 Closeable (用于关闭连接管理器)。
我想你会想使用ThreadSafeClientConnManager。
您可以在这里查看它是如何工作的:http://foo.jasonhudgins.com/2009/08/http-connection-reuse-in-android.html
或者在内部使用它的
AndroidHttpClient
中。