[Java] 並列処理 (Thread, Runnable)
Javaで並列処理(マルチスレッド)の実装についてのまとめです。
Javaで並列処理を実装するにはいくつか方法がありますが、ここではThreadクラスを継承する方法と、Runnableインターフェイスを実装したクラスを用意して、Threadクラスのインスタンスを生成する方法についてまとめます。
基本的な並列処理の実装
Threadクラスを継承する
実装
import java.lang.Thread;
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("sub:" + i);
}
}
};
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main:" + i);
}
}
}
実行結果
実行結果は順不同で出力され、並列に処理されているのが分かります。
java ThreadTest.java main:0 sub:0 main:1 sub:1 main:2 sub:2 main:3 sub:3 main:4 sub:4
Runnableインターフェースを実装する
実装
import java.lang.Runnable;
import java.lang.Thread;
public class ThreadTest {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("sub:" + i);
}
}
};
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main:" + i);
}
}
}
実行結果
こちらも順不同で出力されます。
java ThreadTest.java main:0 sub:0 main:1 sub:1 main:2 sub:2 main:3 sub:3 main:4 sub:4
並列処理にならないケース
Runnableインターフェースを実装し、runメソッドを呼び出しただけでは並列処理にはなりません。
実装
import java.lang.Runnable;
public class ThreadTest {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("sub:" + i);
}
}
};
r.run();
for (int i = 0; i < 5; i++) {
System.out.println("main:" + i);
}
}
}
実行結果
このコードは並列処理になりません。何度実行してもコールした順に処理が実行されます。
java ThreadTest.java sub:0 sub:1 sub:2 sub:3 sub:4 main:0 main:1 main:2 main:3 main:4
- 並列処理はThreadクラスのstart()メソッドにより開始される。
- Runnableインターフェースのrun()メソッドをオーバーライドし、スレッドで実行したい処理を記述する。
- ThreadクラスはRunnableインターフェースを継承している。
排他制御
マルチスレッドアプリケーションで複数スレッドから一つのインスタンスを共有・変更する場合、競合に気を付けなくてはいけません。
排他制御を考慮していない例
コード
以下のコードはMyCounterクラスのインスタンスを複数のスレッドから更新している例です。
しかしこのコードには問題があり、更新するaddメソッド内で値を取り出しから更新が完了するまでタイムラグがあるため高確率で競合が発生します。(ここでは意図的に100ミリ秒のSleepを入れてます。)
import java.lang.Runnable;
import java.lang.Thread;
public class ThreadTest {
class MyCounter {
int count;
}
MyCounter c;
ThreadTest() {
c = new MyCounter();
c.count = 0;
}
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
t.test();
}
public void test() {
Runnable r = new Runnable() {
@Override
public void run() {
add(300);
}
public void add(int count) {
System.out.println(Thread.currentThread().getId() + ":add start");
int temp = c.count;
sleep(100);
c.count = temp + count;
System.out.println(Thread.currentThread().getId() + ":add end");
}
};
Thread[] t = new Thread[3];
for (int i = 0; i < t.length; i++) {
t[i] = new Thread(r);
t[i].start();
}
sleep(1000);
System.out.println(c.count);
}
public void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
}
}
実行結果
本来3つのスレッドで処理をしているので結果は300と出てほしいところですが、競合が発生しているため結果は100になります。
java ThreadTest.java 15:add start 14:add start 16:add start 14:add end 15:add end 16:add end 50
排他制御を考慮した例
上記のコードで発生する競合を解消するにはsynchronizedキーワードを使用します。
コード
以下のようにaddメソッドにsynchronizedキーワードを追加します。
public synchronized void add(int count) {
こうすることでaddメソッドが一つのスレッドから呼び出されているときは、他のスレッドは待ち状態となるので、複数スレッドから同時に更新されるという競合がなくなります。
また、以下のように記述することもできます。
public void add(int count) {
synchronized (this) {
System.out.println(Thread.currentThread().getId() + ":add start");
int temp = c.count;
sleep(100);
c.count = temp + count;
System.out.println(Thread.currentThread().getId() + ":add end");
}
}
実行結果
synchronizedキーワードを付けて実行すると、結果は3つのスレッドから100ずつ加算され、期待通りの300になります。
また、addメソッドのstartとendのログを確認すると、一つずつ処理されているのが分かります。
java ThreadTest.java 14:add start 14:add end 15:add start 15:add end 16:add start 16:add end 300
コメント
コメントを投稿