Javaでの並行性プログラミング
マルチコアおよびマルチCPUのシステムが普通に使われるようになった現在では、複数のタスクを同時に実行することが現実的な課題となっています。しかし、大抵のシステムでは、別のスレッドにタスクを実行させるというような簡単なやり方でこれに対応することはできません。使用するプログラミング言語から実行環境に問い合わせて、システムリソースの使用スケジュールを決定する必要があります。ハイレベルの並行性にかかわる関数を簡単に活用できるかどうかは、言語の構造に左右されます。
従来は、「ヘルパー」を利用して複数のスレッドを行き来する方法がとられてきました。CPUは、あるスレッド上で行われているネットワークやI/O活動が完了するのを待つ間に、別のスレッドを実行して何らかの処理を行い、最初のスレッドの処理が完了して次に進める状態になったら再びそのスレッドに戻ることができます。この方法はアプリケーションの応答性を高めることには有効ですが、それでもコンピュータは一度に1つの作業しか行っていません。
Java言語とJVMが登場する以前は、こうした並行プログラミングモデルを理解する人々は一部の上級開発者に限られていて、しかも処理能力を余すことなく利用するレベルには達していませんでした。並行性にかかわる構造が言語に直接組み込まれていなかったため、どうしてもどれか適当なスレッド処理ライブラリを選び、それに頼らざるを得なかったのです。これらのスレッド処理ライブラリをベースに作られたライブラリを使用すると、他の方式と適合しないことが度々ありました。POSIX標準化は一定の効果をもたらしましたが、その複雑さを解消することにはならず、ほとんどのソフトウェアエンジニアは手を出せませんでした。
Javaは、この並行性の課題に取り組むために、比較的簡単でクロスプラットフォーム性の高い機構を言語およびJVMのレベルに導入しました。Javaがスレッド、すなわちRunnableインターフェースとモニタをサポートしたことは画期的でした。
初期の一部のJVMは、まだ「グリーンスレッド」しか提供されていなかった時期に、ネイティブスレッドをサポートしました。これこそが、ネイティブスレッドの並行性に対応するJVMの舞台裏のマジックでした。これらの基本的なツールを用いることで、次のような移植性の高いマルチスレッドコードを簡単に記述できるようになりました。
Thread t1 = new Thread() { public void run() { System.out.println("Hello from Thread 1!"); } } Thread t2 = new Thread() { public void run() { System.out.println("Hello from Thread 2!"); } } t1.start(); t2.start();
この方法の良い点は、簡単にコーディングできることでした。悪い点は、Thread
インスタンスの乱用がオーバーヘッドを増やし、スケーラビリティを低下させることでした。大量のスレッドを作成すれば、アプリケーションは当然、スレッド間のコンテキストの切り替えのために立ち行かなくなります。開発者は、新規のThread
オブジェクトをできるだけ作成しないで、ある種のスレッドプールで実行するようにスケジュールされたRunnable
インスタンスを使うよう推奨されました。標準のThreadPool
インスタンスと呼べるものが存在しない時期が数年続きましたが、その間に多くの効果的な方法が開発され、広く使われるようになりました。例えば次のようなものです。
Runnable r1 = new Runnable() { public void run() { System.out.println("It's good to be an r1 Runnable!"); } }; Runnable r2 = new Runnable() { public void run() { System.out.println("It's good to be an r2 Runnable!"); } } // Create a ThreadPool with 3 threads waiting for something to do ThreadPool tp = new ThreadPool(3); tp.execute(r1); tp.execute(r2);
このモデルはクライアントの要求をスレッドプールによって処理するという方式で、スケーラビリティの点で無理がなく、最終的に多くの組織でサーバーインフラストラクチャのベースとして使われるようになりました。スレッドのプールによってクライアントの要求を処理するという考え方です。ほとんどのサーブレットエンジンはこのような仕組みの下で動作していますが、シックSwingクライアントでも、長時間実行されるタスク(データベースの問い合わせや、別のスレッドへのRMI要求の発行など)をオフロードすることで応答性の向上が見られます(そうでなければSwingアプリケーションは応答性が低くて人気が出なかったことでしょう!)。