スレッド を用いることにより、ひとつのプログラム(プロセス)の中で複数の処理の流れを走らせることができます。スレッドを作成するには、Thread のサブクラスを作成する方法と、Runnable インタフェースを実装したオブジェクトを用いる方法があります。
Thread クラスのサブクラスを作成することにより、スレッドを作成する方法を以下に示します。Thread クラスのサブクラスを定義し、そのインスタンスを生成し、start() メソッドを呼び出すことで、ThreadTestThread クラスの run() メソッドが実行されます。
class ThreadTest { public static void main(String[] args) { ThreadTestThread tt = new ThreadTestThread(); tt.start(); for (int i = 0; i < 1000; i++) { System.out.print('.'); } } } class ThreadTestThread extends Thread { public void run() { for (int i = 0; i < 1000; i++) { System.out.print('o'); } } }
すでに他のクラスを継承しているサブクラスをスレッド化したい場合は、Thread クラスのサブクラスとして定義するのではなく、代わりに Runnable インタフェースを実装させます。このクラスのインスタンスを引数にして Thread クラスのインタフェースを作成し、start() メソッドを呼び出します。
class RunnableTest { public static void main(String[] args) { RunnableTestThread tt = new RunnableTestThread(); Thread t = new Thread(tt); t.start(); for (int i = 0; i < 1000; i++) { System.out.print('.'); } } } class RunnableTestThread implements Runnable { public void run() { for (int i = 0; i < 1000; i++) { System.out.print('o'); } } }
複数のスレッドが同時に処理を行ってしまうとまずい場合があります。例えば下記の例では、1000個のスレッドを実行し、それぞれがカウンターをひとつずつカウントアップするので結果は 1000になるはずですが、結果が 995になったりします。これは、カウンター値を読み出して設定するまでの間に他のスレッドが割り込んでしまい、同じ値を読み出して同じ値を設定してしまうために発生します。
// テストプログラム class SyncTest { static Counter counter = new Counter(); public static void main(String[] args) { // スレッドを1000個作成する MyThread[] threads = new MyThread[1000]; for (int i = 0; i < 1000; i++) { threads[i] = new MyThread(); threads[i].start(); } // スレッドがすべて終了するのを待つ for (int i = 0; i < 1000; i++) { try { threads[i].join(); } catch (InterruptedException e) { System.out.println(e); } } // カウンターを表示する System.out.println(SyncTest.counter.count); } } // スレッド class MyThread extends Thread { public void run() { SyncTest.counter.countUp(); } } // カウンター class Counter { int count; void countUp() { System.out.print("["); int n = count; // カウンターを読み出して System.out.print("."); count = n + 1; // 加算して書き戻す System.out.print("]"); } }
下記に実行結果の例を示します。[ と ] の対応がとれていない部分が、複数のスレッドが処理を同時実行してしまっている部分です。
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][[[[[[[[[........]]]]]]]].][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.]983
この問題を防ぐには、同期処理 や 排他制御 と呼ばれる制御を行います。下記のように synchronized を用いることで、(...) で指定したオブジェクト(下記の例では this、つまり Counter オブジェクト)に対してロック権を取得した単一のスレッドのみが { ... } の処理を実行できるようになります。
void countUp() { synchronized (this) { System.out.print("["); int n = count; // カウンターを読み出して System.out.print("."); count = n + 1; // 加算して書き戻す System.out.print("]"); } }
上記は、下記のように書き表すこともできます。
synchronized void countUp() { System.out.print("["); int n = count; // カウンターを読み出して System.out.print("."); count = n + 1; // 加算して書き戻す System.out.print("]"); }
これにより、起動したスレッドの数だけ正常にカウントを行うことができるようになります。
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.]1000
synchronized メソッドは、常にひとつのスレッドのみがそのメソッドを実行すると誤解されがちですが、スレッドを実行するインスタンスが複数あれば、インスタンスの個数だけ多重に実行される可能性がありますので注意してください。例えば、上記の例で add() ではなく run() メソッドを synchronized にしても、run() メソッドのインスタンスはスレッドの個数分 1000個ありますので、排他制御はうまく機能しません。
synchronized public void run() { // 駄目な例 SyncTest.counter.countUp(); }
グローバルに参照可能なロック用オブジェクトを作成しておき、排他制御を行いたい箇所で下記のように使用する方法もあります。
class Global { static Object lock = new Object(); } class Counter { void countUp() { synchronized (Global.lock) { // 排他制御を行いたい箇所 } } }