tags:

views:

241

answers:

1

I wrote code that can send http requests in parallel way with multiple threads. The parent thread uses thread.join() to wait for all child threads to finish. But in my code, join() didn't work sometimes. Could someone point out what's wrong with my code? Thanks.

/**
 * @param urlSet
 *            , a set of link URLs (Can not be null)
 * @return Map<link URL, response in byte array>
 * @throws InterruptedException
 */
protected Map<String, byte[]> multiProcessRequest(Set<String> urlSet)
        throws InterruptedException {

    if (urlSet.isEmpty()) {
        return null;
    }

    // Create and initialize HTTP parameters
    HttpParams params = new BasicHttpParams();
    ConnManagerParams.setMaxTotalConnections(params, MAX_CONNECTIONS);
    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

    // Create and initialize scheme registry
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme("http", PlainSocketFactory
            .getSocketFactory(), 80));

    // Create an HttpClient with the ThreadSafeClientConnManager.
    // This connection manager must be used if more than one thread will
    // be using the HttpClient.
    ClientConnectionManager cm = new ThreadSafeClientConnManager(params,
            schemeRegistry);
    m_httpClient = new DefaultHttpClient(cm, params);

    List<String> urlList = new ArrayList<String>(urlSet);

    // create a thread for each URI
    GetThread[] threads = new GetThread[urlSet.size()];

    for (int i = 0; i < threads.length; i++) {
        HttpGet httpget = new HttpGet(urlList.get(i));
        threads[i] = new GetThread(m_httpClient, httpget, i + 1);
    }

    // start the threads
    for (int j = 0; j < threads.length; j++) {
        threads[j].start();
    }

    // join the threads
    for (int j = 0; j < threads.length; j++) {
        threads[j].join();
    }

    // FIXME: debug for statement only
    for (int j = 0; j < threads.length; j++) { 
        if (threads[j].isAlive()) {
            s_logger.debug("Thread " + (j+1) + " is still alive : " + threads[j].getState()); 
        }
    }

    // When HttpClient instance is no longer needed,
    // shut down the connection manager to ensure
    // immediate deallocation of all system resources
    m_httpClient.getConnectionManager().shutdown();

    s_logger.debug("ConnectionManager shutted down.");

    /* Prepare the return. */
    Map<String, byte[]> urlToResponseMap = new HashMap<String, byte[]>(
            threads.length);

    for (int i = 0; i < threads.length; ++i) {
        urlToResponseMap.put(urlList.get(i), threads[i].getResult());
    }

    return urlToResponseMap;
}

/**
 * A thread that performs a GET.
 */
static class GetThread extends Thread {

    private final HttpClient httpClient;
    private final HttpContext context;
    private final HttpGet httpget;
    private final int internalId;

    /** The response result of the URL get. */
    private byte[] result;

    public GetThread(HttpClient httpClient, HttpGet httpget, int id) {
        this.httpClient = httpClient;
        this.context = new BasicHttpContext();
        this.httpget = httpget;
        this.internalId = id;
    }

    public byte[] getResult() {
        return result;
    }

    /**
     * Executes the GetMethod and prints some status information.
     */
    @Override
    public void run() {

        s_logger.debug(internalId + " - about to get something from "
                + httpget.getURI());

        try {

            // execute the method
            HttpResponse response = httpClient.execute(httpget, context);

            s_logger.debug(internalId + " - get executed");

            // get the response body as an array of bytes
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                result = EntityUtils.toByteArray(entity);

                s_logger.debug(internalId + " - " + result.length
                        + " bytes read from " + httpget.getURI());
                s_logger.debug(internalId + ": " + result);
            }

        } catch (Exception e) {
            httpget.abort();
            s_logger.error(internalId + " - error: ", e);
        }
    }

}

In the log, I saw these debug messages after running multiProcessRequest():

D/GetThread:run( 964): 16 - get executed D/GetThread:run( 964): 16 - 9444 bytes read from ...... D/dalvikvm( 964): GC freed 675 objects / 463256 bytes in 84ms D/GetThread:run( 964): 16: [B@4383add8 D/GetThread:run( 964): 17 - get executed D/GetThread:run( 964): 17 - 9000 bytes read from ...... D/GetThread:run( 964): 17: [B@437f5240 D/GetThread:run( 964): 18 - get executed D/:multiProcessRequest( 964): Thread 18 is still alive : RUNNABLE

D/:multiProcessRequest( 964): Thread 20 is still alive : RUNNABLE D/dalvikvm( 964): threadid=17 wakeup: interrupted

D/:multiProcessRequest( 964): ConnectionManager shutted down.

D/GetThread:run( 964): 18 - 9427 bytes read from ...... D/$GetThread:run( 964): 18: [B@438412e8 D/dalvikvm( 964): GC freed 1269 objects / 666456 bytes in 90ms W/ExpatReader( 964): DTD handlers aren't supported. D/$GetThread:run( 964): 20 - get executed D/$GetThread:run( 964): 20 - 12751 bytes read ...... D/$GetThread:run( 964): 20: [B@43836dc0

These two lines below showed that two threads were still running after join():

D/:multiProcessRequest( 964): Thread 18 is still alive : RUNNABLE

D/:multiProcessRequest( 964): Thread 20 is still alive : RUNNABLE

A: 

Hmmm..., your code looks good to me, I can't see how any thread could be alive after your joins. Unless join throws an exception, but in that case your logging statements wouldn't fire.

Have you tried this on another JVM or platform?

Keith Randall
I only tried it on the Android simulator because I can see the log. Let me try this code on a JVM.
I tried the same code on JVM and didn't reproduce the problem. It is weird. It looks like at least the Android simulator has thread synchronization problem.Anyways, thank you Keith.
No problem. You should file a bug against the Android emulator.
Keith Randall
Filed a bug. Android guys said it is a known bug in Android 1.5, but has been fixed in Android 2.x. So it is fine now.