第28夜 Javaの高機能非同期HTTPクライアント

最近Javaネタあんまり書いてなかったので久々に。

の続き。

今回は非同期APIを利用してみる。

いままでやっていたのはすべて同期APIだった。通信が完了するまでブロックしていた。複数のリクエストを投げたい場合はスレッドを新しく作ってやる必要があった。

だが、そんなことは必要ない。JAX-RS RIのJersey HTTPクライアントはデフォで非同期APIがある。

まず、同期方式で2つのファイルを取得してみる。

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;

public class Sync {

    public static void main(String[] args) {
        Client client = Client.create();

        long start = System.nanoTime();
        WebResource wr1 = client.resource("https://sites.google.com/site/shin68k/");
        WebResource wr2 = client.resource("https://sites.google.com/site/shin68k/product/purewind");

        String result1 = wr1.get(String.class);
        String result2 = wr2.get(String.class);

        long end = System.nanoTime();
        System.out.println(result1);
        System.out.println(result2);

        System.out.println((end-start)/1000/1000+"ms");
    }

}

2つのgetがそれぞれブロックされるので単純に取得した時間が足し算でかかる。
URLはなんでもいいけど、google siteがレスポンス劇重なのでテストするにはちょうどよい。

続いて非同期バージョン。

import com.sun.jersey.api.client.AsyncWebResource;
import com.sun.jersey.api.client.Client;
import java.util.concurrent.Future;

public class Async {

    public static void main(String[] args) throws Exception {
        Client client = Client.create();

        long start = System.nanoTime();

        AsyncWebResource awr1 = client.asyncResource("https://sites.google.com/site/shin68k/");
        AsyncWebResource awr2 = client.asyncResource("https://sites.google.com/site/shin68k/product/purewind");
        Future<String> future1 = awr1.get(String.class);
        Future<String> future2 = awr2.get(String.class);

        String result1 = future1.get();
        String result2 = future2.get();

        long end = System.nanoTime();

        System.out.println(result1);
        System.out.println(result2);
        System.out.println((end-start)/1000/1000+"ms");

        client.getExecutorService().shutdown();
    }

}

見てわかるとおりjava.util.concurrent.Futureが戻り値である。シンプルだ。
最後にプーリングしたスレッドを終了している。コンカレントAPIを使っていることからわかるようにスレッドの作成方法はカスタマイズは容易だ。Client に対して設定するとよい。

結果を取得時にブロックするgetで結果的に一番重い取得に引っ張られるようにしている。
それぞれ5回の計測結果。

同期 非同期
1回目 1913ms 1437ms
2回目 1605ms 969ms
3回目 1619ms 727ms
4回目 1620ms 1194ms
5回目 1813ms 842ms
平均 1714ms 1034ms

GoogleSiteはレスポンスが安定しないものの、平均を見ると圧倒的に非同期が早いのがわかると思う。

クライアントの通信は非同期で行うことが多いため、スレッドを自ら立てずに行えるのは非常にありがたい。