在實際應用中,網絡爬蟲(網絡蜘蛛)往往需要每分鍾爬取上百個網頁甚至更多,這樣的要求,單線程的爬蟲(蜘蛛/spider)實在難以完成,勢必要引入多線程技術。而在多線程環境下,為了避免浪費資源,需要重用HttpClient實例,由此帶來了資源爭搶和同步的問題。
在HttpClient3的時候,我們可以使用池化技術來自行實現一個HttpClient池,滿足這樣的需要,但是在HttpClient4中,相關的解決方案已經內置其中,於是我們不必大費周章地實現一個對象池,來手動管理HttpClient實例,這樣我們有更多的時間和精力來專注頁面內容的處理。
接下來,我們將演示如何使用ThreadSafeClientConnManager實現在多線程環境中使用同一個HttpClient對象,已達到節約資源的目的。
package cn.ysh.studio.crawler.httpclient;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.HttpClient;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;import org.apache.http.protocol.BasicHttpContext;import org.apache.http.protocol.HttpContext;import org.apache.http.util.EntityUtils;/** * 在多線程環境中使用同一個HttpClient對象 * * @author Shenghany * @date 2013-5-19 */publicclassClientMultiThreadedExecution{publicstaticvoid main(String[] args)throwsException{// 創建一個使用ThreadSafeClientConnManager連接管理器的HttpClient實例// 當這個HttpClient實例被超過1個線程使用時,必須使用ThreadSafeClientConnManagerThreadSafeClientConnManager tscm =newThreadSafeClientConnManager();HttpClient httpclient =newDefaultHttpClient(tscm);try{// 創建一個需要請求的URL數組String[] urisToGet ={"http://www.yshjava.cn","http://hc.apache.org/","http://hc.apache.org/httpcomponents-client-ga/","http://svn.apache.org/viewvc/httpcomponents/"};// 為每一個URL創建一個請求線程GetThread[] threads =newGetThread[urisToGet.length];for(int i =0; i < threads.length; i++){HttpGet httpget =newHttpGet(urisToGet[i]); threads[i]=newGetThread(httpclient, httpget, i +1);}// 啟動所有請求線程for(int j =0; j < threads.length; j++){ threads[j].start();}// join所有請求線程for(int j =0; j < threads.length; j++){ threads[j].join();}}finally{// 當不再需要HttpClient實例時,關閉連接管理器以確保釋放所有占用的系統資源 httpclient.getConnectionManager().shutdown();}}/** * 執行Get請求的線程 */staticclassGetThreadextendsThread{privatefinalHttpClient httpClient;privatefinalHttpContext context;privatefinalHttpGet httpget;privatefinalint id;publicGetThread(HttpClient httpClient,HttpGet httpget,int id){this.httpClient = httpClient;this.context =newBasicHttpContext();this.httpget = httpget;this.id = id;}/** * 執行Get請求並打印一些狀態信息 */@Overridepublicvoid run(){System.out.println(id +" - about to get something from "+ httpget.getURI());try{// 執行Get請求HttpResponse response = httpClient.execute(httpget, context);System.out.println(id +" - get executed");// 獲得以字節數組接收響應實體內容HttpEntity entity = response.getEntity();if(entity !=null){byte[] bytes =EntityUtils.toByteArray(entity);System.out.println(id +" - "+ bytes.length +" bytes read");}}catch(Exception e){ httpget.abort();System.out.println(id +" - error: "+ e);}}}}
注:可以考慮工作隊列 工作線程