接上篇分析FetcherQueue,这个大概是最麻烦的方法了,通过这个方法实现线程池进而实现异步操作,并将resquest和httpclient对象结合起来,同时还维护请求队列,并负责调用上层传下来的ConversationHandler方法。
首先从构造函数说起:
public FetcherQueue(String name, ConversationHandler handler, int threads, int requestDelay) { _handler = handler; _fetchers = new Fetcher[threads]; _requestDelay = requestDelay; for (int i=0; i<threads; i++) { _fetchers[i] = new Fetcher(name+"-"+i); } start(); }
public的构造函数,说明这不是一个单例。
传入的第一个参数是名字,根据这个名字为线程池中的线程命名,然后第二个参数传入一个handler,根据这个handler来决定response到来后如何处理,第三个参数是线程池中的线程数量,第四个是request延迟,也就是当request到来时延迟多少毫秒再递交给urlFetcher处理。
其次调用start方法。
通过这个构造函数我们可以得到以下信息:
线程池中线程的数量是固定的,一个FetcherQueue只能由一个Handler,并且requestDelay也在创建的时候就确定了。
ConverSationHandler接口声明如下:
public interface ConversationHandler { void responseReceived(Response response); void requestError(Request request, IOException ioe); }
很简单,也就是一个接收response的方法,以及一个出io错误的方法。
接下来看start方法:
public void start() { _running = true; for (int i=0; i<_fetchers.length; i++) { _fetchers[i].start(); } }
置_running为true,然后调用每个线程的start。看到running标志相信都能够猜到每个线程的运行大概是一个while(_running)循环了。
值得注意的是,start()算是一个异步方法,启动完线程池中的所有线程后就返回了,然后构造函数结束,主线程(确切的说是构造fetcherQueue的线程)可以得到一个fetchQueue对象,并接着执行下面的操作。
接下来我们再看线程池中的线程到底做了什么工作。
private class Fetcher extends Thread { public Fetcher(String name) { super(name); setDaemon(true); setPriority(Thread.MIN_PRIORITY); } public void run() { while (_running) { Request request = getNextRequest(); try { Response response = HTTPClientFactory.getInstance().fetchResponse(request); response.flushContentStream(); responseReceived(response); } catch (IOException ioe) { requestError(request, ioe); } } } }
while(_running)之后尝试得到一个request对象,然后调用HttpClientFactory的方法获取HttpClientFactory的实例(注意不是HttpClient的实例),然后调用HTTPClientFactory的fetchResponse方法,得到response后首先将内容flush出来(这部分参见第二篇博客),然后调用handler中的response方法。
值得注意的有两点:
1、所有线程池中的线程都运行这个方法,换句话说这个方法是并行的,因此在getNextRequest()里肯定有控制同步的逻辑。
2、handler里面的逻辑,也就是response的处理是使用线程池里的线程执行的,传入handler的时候要考虑到跨线程调用的数据一致性问题。
3、当一个线程处于可用的时候是指其运行在getNextRequest()这个方法,并被阻塞的时候。
接着看getNextResquest方法:
private Request getNextRequest() { synchronized (_requestQueue) { while (_requestQueue.size() == 0) { try { _requestQueue.wait(); } catch (InterruptedException ie) { // check again } } if (_requestDelay > 0) { long currentTimeMillis = System.currentTimeMillis(); while (currentTimeMillis < _lastRequest + _requestDelay) { try { Thread.sleep(_lastRequest + _requestDelay - currentTimeMillis); } catch (InterruptedException ie) {} currentTimeMillis = System.currentTimeMillis(); } _lastRequest = currentTimeMillis; } _pending++; return (Request) _requestQueue.remove(0); } }
首先尝试锁住_requestQueue,然后如果当前queue没有数据,则wait,线程挂起。否则延迟后将queue中的第一个request返回,释放锁。
当多个线程重入这个方法时,因为锁的存在,只有一个线程能运行锁中代码,若队列为空,则这个线程调用wait挂起自己,释放锁,其余线程依次进入临界区,调用wait并释放锁,此时所有线程都挂起。。
下面的考虑,获得锁的线程是如何被唤醒的。也就是submit方法。
public void submit(Request request) { synchronized (_requestQueue) { _requestQueue.add(request); _requestQueue.notify(); } }
getnextRequest是线程池中线程调用的方法,而submit是创建fetcherQueue的线程调用的方法,从名字可以看出,这个方法提交请求。
所谓提交首先锁住_requestQeueu,然后添加元素,然后唤醒所有等待的线程。唤醒后因为锁的存在,只有一个线程能得到request对象,该线程的getNextRequest返回,并继续执行线程中的代码,其余线程池的线程继续等待。
值得注意的是submit也是一个异步方法,提交后直接返回,并没有任何等待或者处理的操作,所有和response的操作都是放在handler里执行的。
至此fetcherQueue的分析也差不多结束了,根据几篇博客的分析我裁减出来了一个简单的http栈,代码已经上传到资源。(说道裁减其实就是把model、httpclient、util这3个包原封不动的拷贝出来而已。。。。)