WebScarab关键源码分析(5)

来源:互联网 发布:南风知我意 琰阙全文 编辑:程序博客网 时间:2024/04/29 23:57

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