Aritalab:Lecture/Programming/Java/Parallel
Javaによる並列プログラミング
並列プログラミングの基本は
- スレッド
- 排他制御
- 同期制御
になります。個々の概念をサンプルプログラムを用いて解説します。
スレッド
コンピュター上のアプリケーション・プログラムは、OSからみると「プロセス」という単位で実行されています。個々のプロセスはメモリ空間やファイル入出力などを独自に備えています。(個々のアプリケーションプログラムは独立ですから、当たり前です。)並列プログラミングを行う場合、こうしたプロセスを複数作成して並列処理を実装することも可能ですが、複数プロセスを操作するにはOSのカーネルモードとユーザーモード(アプリケーション自身のモード」を行き来することになります。このオーバーヘッドは望ましくありません。そこで単一のプロセス内に(つまりメモリ空間やファイルデスクリプタを共有した形で)複数の実行単位を持たせたものを「スレッド」と呼びます。以前は軽量 (lightweight) プロセスとも呼ばれました。
スレッドの作成
まずは以下のプログラムを実行してみてください。このプログラムは2つのスレッドを作成し、それぞれ 0-50 ms 休みながら数字をカウントして終了します。実行するたびに出力結果が異なることを確認して下さい。また、メイン関数から呼ばれる test() の処理がスレッドよりも早く終了することも確認して下さい。
import java.util.Random; class Counter extends Thread { Random rand = new Random(); String indent; public Counter(int c) { StringBuffer sb = new StringBuffer(); for(int i=0; i < c; i++) sb.append('\t'); indent = sb.toString(); } public void run() { System.out.println(indent+"ready"); try { for (int i=0; i < 5; i++) { Thread.sleep(rand.nextInt(50)); // sleep for 0-50 ms System.out.println(indent+"count: " + i); } } catch (Exception e) { e.printStackTrace(); } System.out.println(indent+"done"); } } public class ThreadTest { static void test() { Counter thread1 = new Counter(0); Counter thread2 = new Counter(1); System.out.println("START"); thread1.start(); thread2.start(); System.out.println("FINISH"); } public static void main(String[] args) { ThreadTest.test(); } }
実行例(毎回異なる)
START FINISH ready ready count: 0 count: 1 count: 2 count: 0 count: 1 count: 3 count: 2 count: 4 done count: 3 count: 4 done
Java におけるスレッドは二通りの作成法があります。
- Thread クラスを継承する
- Runnable インターフェースを継承する
最初のほうが簡単ですが、後者のほうが(自分でクラスのデザインをできるぶん)柔軟です。Counter テストプログラムの場合、Runnable を用いて書くには以下のようにします。
class Counter implements Runnable { ... } static void test() { Counter thread1 = new Counter(0); Counter thread2 = new Counter(1); System.out.println("START"); new Thread(thread1).run(); new Thread(thread2).run(); System.out.println("FINISH"); }
つまり、クラス定義の部分で implements Runnable 「インターフェース」を継承します。こうすると run() というメソッドを必ず実装しなくてはなりません。extends Thread のようにクラスを継承すると、デフォールトの run() メソッドとしてThread クラスの run() を継承しますが、implements Runnable の場合は名前の通り必要なメソッドを必ず実装します。この違いはC++と異なり Java は多重継承を許さない点に起因します。例えば Applet クラスでスレッドを使いたい場合、Thread と Applet と両方を継承できないので Runnable というインターフェースを継承するのです。