接上篇分析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个包原封不动的拷贝出来而已。。。。)