はじめてのJava ~マルチスレッド編~
スポンサーリンク
今回はJavaにおけるマルチスレッドの実装をやっていきます。
マルチスレッドでは複数のスレッドを並行処理できるため効率的にプログラムを実行できますが、設計を誤ると結果が想定と異なってしまうなどの危険性もあります。
しっかり覚えて使いこなしましょう。
マルチスレッドプログラミング
スレッドの作成方法は2種類あります。
スレッドの作成1
Threadクラスを拡張する方法です。
スレッドの処理をrun()メソッドに記述し、start()メソッドでスレッドの実行を開始します。
class Thread1 extends Thread { public void run() { try { while(true) { Thread.sleep(500); System.out.println("Thread1"); } } catch(InterruptedException e) { e.printStackTrace(); } } }
class Thread2 extends Thread { public void run() { try { while(true) { Thread.sleep(500); System.out.println("Thread2"); } } catch(InterruptedException e) { e.printStackTrace(); } } }
class ThreadMainA { public static void main(String args[]) { Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.start(); t2.start(); } }
// 出力 Thread2 Thread1 Thread1 Thread2 Thread2 Thread1 Thread2 Thread1 ・ ・ ・
同じ間隔で出力しているためシングルスレッドであれば「Thread1→Thread2→Thread1→Thread2→・・・」となるはずですが、マルチスレッドにしたので順番になりません。
スレッドの作成2
Runnableインタフェースを実装したクラスを宣言する方法です。
run()メソッドにスレッドの処理を記述するところは先程と同じですが、Threadコンストラクタの引数にRunnableインタフェースを実装したクラス(Runnable1、Runnable2)のオブジェクトを渡すところが異なります。
class Runnable1 implements Runnable { public void run() { try { while(true) { Thread.sleep(500); System.out.println("Runnable1"); } } catch(InterruptedException e) { e.printStackTrace(); } } }
class Runnable2 implements Runnable { public void run() { try { while(true) { Thread.sleep(500); System.out.println("Runnable2"); } } catch(InterruptedException e) { e.printStackTrace(); } } }
class ThreadMainB { public static void main(String args[]) { Runnable1 r1 = new Runnable1(); Runnable2 r2 = new Runnable2(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } }
// 出力 Runnable1 Runnable2 Runnable2 Runnable1 Runnable2 Runnable1 Runnable2 Runnable1 ・ ・ ・
同期
マルチスレッドで処理を行うことにより不具合が起こることがあります。
共通の資源に同時にアクセスしてしまうことで、あるスレッドの処理が無視されてしまうのです。
例えば以下のような場合。
class Calc { int num; void increment() { num++; } }
class Call extends Thread { Calc c; Call(Calc c) { this.c = c; } public void run() { try { c.increment(); } catch(Exception e) { e.printStackTrace(); } } }
class SyncMain { private final static int LNUM = 100000; public static void main(String args[]) { Calc calc = new Calc(); Call call[] = new Call[LNUM]; // スレッドの作成と開始 for(int i = 0; i < LNUM; i++) { call[i] = new Call(calc); call[i].start(); } // スレッドがすべて終了するまで待機 for(int i = 0; i < LNUM; i++) { try { call[i].join(); } catch(InterruptedException e) { e.printStackTrace(); } } System.out.println(calc.num); } }
// 出力 99990
これは、初期値0の1つの変数にマルチスレッドで100000回”+1”するプログラムです。
結果は100000になってほしいのですが、99990になってしまっています。
理由を図で説明します。
シングルスレッドであれば以下のように計算されます。
しかし、マルチスレッドにすることで、以下のようなことが起こる可能性があります。
このような計算では、あるスレッドが資源を使っているときは他のスレッドは資源を使えないようにロックをかけなくてはなりません。
それを行う機能が同期です。
「synchronized」を使いますが、修飾子として使う方法とブロックで使う方法があります。
先程のCalcクラスを以下のように変更します。
// 修飾子として使う class Calc { int num; synchronized void increment() { num++; } }
または、
// ブロックで使う class Calc { int num; void increment() { synchronized(this) { num++; } } }
変更後は、いずれも出力は100000になります。