今まで,この連載では月ごとにテーマを決めて解説を行うというスタイルで行ってきました。今月はちょっと変則的なのですが,月の前半と後半に分けてみます。

というのも,今月の15日から4日間,Javaの最大のお祭りJavaOneがサンフランシスコで開催されるからです。また,前日の4月14日にはNetBeans Dayも開催されます。

4日間の会期中,テクニカルセッション,BOF,ハンズオンラボを含めて300以上のセッションが,朝8時30分から夜中の11時30分までびっちりと行われます。まさに,Java漬けの一週間です。

筆者もJavaOneに参加するので,今月の後半はJavaOneのレポートをお送りする予定です。

ブロックしないということはどういうこと?

さて,話をNew I/Oに戻しましょう。

今回はノンブロッキングI/Oについて取りあげます。

ノンブロッキングI/Oとは,処理をブロックすることなく行うことができるI/O処理のことです。通常のI/O処理,例えば次のようなコードがあったとします。

  InputStream stream = socket.getInputStream();
  int c = stream.read();

ソケットからストリームを取り出して,そこから入力を行うというコードです。このコードを実行すると,readメソッドは実際にデータを受信するまで待ち続けます。

つまり,readメソッドで処理がブロックされるのです。

ノンブロッキングI/Oを使用すると,readメソッドをコールしたときに受信したデータがなければそのまま戻ってきてしまいます。すぐに戻ってきてしまうので,処理がブロックされません。これがノンブロッキングたる所以です。

でも,すぐに戻ってきてしまったら,本当にデータを受信したときにどうやってそれを知ればいいのでしょうか?

心配することはありません。入出力操作を監視するための専用のクラスが用意されているのです。それが,java.nio.channels.Selectorクラスです。

UNIX上でC/C++を使用して通信プログラミングをしたことがある方はご存じだと思いますが,UNIXにはselectというシステムコールが存在します。このselectシステムコールの機能をJavaで実現したのがSelectorクラスなのです。

でも,なぜノンブロッキングI/Oなんてものが必要なんでしょう。今まで普通にソケットを用いて通信ができていたのに,わざわざSelectorクラスなんてものを使わなくてはならないなんて,なんか面倒そうですよね。

その理由の一つにスレッドがあります。

ブロッキングI/Oを使用して複数のソケットを扱う場合を考えてみましょう。

I/O処理がブロックされてしまうので,その間,他の処理はできなくなってしまいます。そのため,ソケットごとにスレッドを割り当てて,マルチスレッドで処理をすることが多いはずです。

例えば,StreamHandlerクラスがRunnableインタフェースを実装したI/O処理を行うクラスだとしましょう。すると,下に示したコードのように,アクセプトされたらスレッドを作成して,StreamHandlerオブジェクトで入出力の処理を行うように記述します。

  while(true) {
    Socket socket = serverSocket.accept();
    Runnable handler = new StreamHandler(socket);
    Thread thread = new Thread(handler);
    thread.start();
  }

扱うソケットが一つや二つならばいいのですが,どんどん増えていってしまったらどうしましょう。扱うソケットの数が増えれば,それだけスレッドが生成されてしまいます。もちろん,スレッドプールを使うこともできます。しかし,できるだけスレッドの数は減らしたいところです。

そこで登場するのが,Selectorクラスです。さきほど,Selectorクラスは入出力操作を監視してくれると書きましたが,複数のソケットをまとめて監視することも可能なのです。

したがって,今まで複数のスレッドでI/O処理を行っていたのが,Selectorクラスを使えば一つのスレッドで処理できるのです。

どうですか,使ってみたくなりませんか?

ノンブロッキングI/Oの実力は?

とはいえ,本当に使えるかどうかは,実際に試してみなければわかりません。

ここでは先月のファイル・コピーでの比較と同様にマイクロベンチマークを使用して,ノンブロッキングI/Oの実力を探ってみましょう。

題材はHTTPサーバーです。ストリームとスレッドプールを使用して実装したものと,ノンブロッキングI/Oを使用して実装したものをサンプルとして用意しました。

サンプルのダウンロード: httpserver.zip

あくまでもサンプルなので,文字コードを全く考慮していなかったり,キャンセルなどの処理も実装していません。とりあえず,比較できる最低限の機能だけを実装しました。また,入出力以外の部分はなるべく同じコードを使用するようにしてあります。例えば,入力文字列のパースなどは同じコードを使用しています。

より深くHTTPサーバーの実装について知りたい方は,arton氏の解説がありますので,そちらをご参照ください

パフォーマンスの測定にはJMeterを使用しました。CPUがPentium4 2.6GHz,メモリーが1GB,OSがWindows XP SP2のマシンでサンプルのHTTPサーバーを実行させています。Javaの実行環境は,J2SE 5.0 update 6をサーバーVMで動作させています。

JMeterを動作させたのはPentium-M 1.86GHz,メモリー1GB,OSは同じくWindws XP SP2のマシンです。このマシンでもJ2SE 5.0 update6を使用しています。通信にはIEEE 802.11gを使用しました。JMeterは10スレッドでリクエストする設定にしています。

下に示したスクリーンショットは,100KBのHTMLファイルをリクエストした結果です。(a)がストリームを使用した場合,(b)がノンブロッキングI/Oを使用した場合です。

ストリームを使用した場合の結果
(a)ストリームを使用した場合の結果
ノンブロッキングI/Oを使用した場合の結果
(b)ノンブロッキングI/Oを使用した場合の結果

注目していただきたいのが,緑で表示されているスループットです。ストリームが1588,ノンブロッキングI/Oが1700になっており,明らかにノンブロッキングI/Oのほうがスループットが高くなっています。

このサンプルもマイクロベンチマークなので,総合的な判断が難しいのは確かです。しかし,ノンブロッキングI/Oのパフォーマンスが十分に高いことはおわかりいただけると思います。

さて,来週はノンブロッキングI/Oを実際に使ってみることにしましょう。

「Javaによる簡易HTTPサーバーの作成」シリーズ(閲覧には会員登録が必要です)

著者紹介 櫻庭祐一

横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名