Edited at

Java再入門 - マルチスレッドプログラミング

More than 1 year has passed since last update.


背景・目的

Javaのスレッドプログラミング再入門。JFRも使いながら、(今度こそ!)スレッドの動きを理解してみる。

随時更新していきます。


参考文献


用語整理


Concurrent Program(並行) vs. Parallel Pragram(並列)

厳密にいうと、以下のように分類できるようだが、文献によって違う解釈だったりするので、ざっくり理解するぐるいにしておこう。


Concurrent(並行)


  • 同じ時間帯の中で、複数のタスクが同時に実行される。


    1. 一つのCPUの場合、タスクは細かく分割され同時に動くように見える。

    2. 複数のCPUで実行される場合は、Parallelと同じ意味



例)Concurrent Tasks on 1 CPU

image


Parallel(並列)


  • 同じ時間帯の中で、複数のタスクが複数のCPUで同時に(並列)動く

  • ParallelプログラムはConcurrentプログラムの一種。

例)Parallel Tasks on 2 CPUs(=Concurrent on 2 CPUs)

image


Threads vs. Fork/Join

ThreadはConcurrent Programming、Fork/JoinはParallel Programming


Thread


  • タスクが比較的大きく、自己完結する場合

  • 待ちが発生する処理などに有効。ネットワーク経由でFileをダウンロードしつつ、別の処理を行う。

  • 1 CPUでも効果的


Fork/Join


  • タスクが大きく、分割して処理し、最終的にまとめられるもの

  • 1 CPUでは意味ない

  • StreamsのParallel版もこの仕組を使う


Threadプログラムの作成

基本的に以下の2つの方法がある。どちらか選択するんだったら、Runnable Interfaceがお勧めらしい。



  1. Runnable interfaceをimplements


  2. Thread classをextends


テスト1 - 自分自身(main)のスレッドを表示

実はJavaのメインプログラム自身も一つのスレッド。特にスレッドを作らずに自分自身のスレッド情報を表示してみよう。


テストシナリオ1-1 - メインスレッド情報を表示する


コード


ThreadTestOneThreadInfo

/** 

* Thread Test - Display One Thread(main Thread) Information
*/

public class ThreadTestOneThreadInfo {
public static void main(String[] args) {
Thread th = Thread.currentThread();

System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());

try {
for (int i = 0; i < 5; i++) {
System.out.println(i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



実行結果

Thread Info - Id:1, Name:main, Priority:5, State:RUNNABLE

0
1
2
3
4
Finishing Thread [main]


Java Flight Recorderのイベント - グラフ画面

スレッドの詳細情報を観るため、Java Flight Recorder(JFR)でレコーディングし、詳細を確認。

レコーディングしたJFRの「イベント」メインタブの「グラフ」を開くと以下のように表示される。

image


テストシナリオ1-2 - メインスレッドの名前を変更する


コード


ThreadTestOneThreadChangeName

public class ThreadTestOneThreadChangeName {

public static void main(String[] args) {
Thread th = Thread.currentThread();

// change Thread Name
th.setName("MyThread");
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());

try {
for (int i = 0; i < 5; i++) {
System.out.println(i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



実行結果

Thread Info - Id:1, Name:MyThread, Priority:5, State:RUNNABLE

0
1
2
3
4
Finishing Thread [MyThread]


Java Flight Recorderのイベント - グラフ画面

image


テスト2 - Childスレッドを作成


テストシナリオ2-1 - Runnable Interfaceをimplements


コード


1) Mainクラス


ThreadTestTwoThreadViaRunnableMain

public class ThreadTestTwoThreadViaRunnableMain {

public static void main(String[] args) {
new ThreadTestTwoThreadViaRunnableChild();

Thread th = Thread.currentThread();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());

try {
for (int i = 0; i < 5; i++) {
System.out.println("Main Thread : " + i);
Thread.sleep(1000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



2) Childスレッド(Runnable Interfaceをimplement)


ThreadTestTwoThreadViaRunnableChild

public class ThreadTestTwoThreadViaRunnableChild implements Runnable {

Thread th;

public ThreadTestTwoThreadViaRunnableChild() {
th = new Thread(this, "Child Thread");
th.start();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());
}

@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Child Thread : " + i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



実行結果

Mainのsleepは1秒、Childのsleepは2秒であるため、Mainループが先に終わっている。

ChildプロセスのIdは13、Nameは「Child Thread」(作るときに指定した名前)。ちなみに、Threadを作る際、Nameを指定しないと「Thread-2」という名前になった。

Thread Info - Id:13, Name:Child Thread, Priority:5, State:RUNNABLE

Thread Info - Id:1, Name:main, Priority:5, State:RUNNABLE
Main Thread : 0
Child Thread : 0
Main Thread : 1
Child Thread : 1
Main Thread : 2
Main Thread : 3
Child Thread : 2
Main Thread : 4
Finishing Thread [main]
Child Thread : 3
Child Thread : 4
Finishing Thread [Child Thread]


JFRでの確認

image


テストシナリオ2-2 - Thread Classをextends


コード


1) Mainクラス


ThreadTestTwoThreadViaThreadMain

public class ThreadTestTwoThreadViaThreadMain {

public static void main(String[] args) {
new ThreadTestTwoThreadViaThreadChild();

Thread th = Thread.currentThread();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());

try {
for (int i = 0; i < 5; i++) {
System.out.println("Main Thread : " + i);
Thread.sleep(1000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



2) Childスレッド(Threadクラスをextend)


ThreadTestTwoThreadViaThreadChild

public class ThreadTestTwoThreadViaThreadChild extends Thread {

public ThreadTestTwoThreadViaThreadChild() {
super("Child Thread");
start(); // start the thread
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
this.getId(), this.getName(), this.getPriority(), this.getState());
}

@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Child Thread : " + i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println("Child Thread interrupted");
}
System.out.printf("Finishing Thread [%s]%n", this.getName());
}
}



実行結果

Thread Info - Id:13, Name:Child Thread, Priority:5, State:RUNNABLE

Thread Info - Id:1, Name:main, Priority:5, State:RUNNABLE
Main Thread : 0
Child Thread : 0
Main Thread : 1
Main Thread : 2
Child Thread : 1
Main Thread : 3
Child Thread : 2
Main Thread : 4
Finishing Thread [main]
Child Thread : 3
Child Thread : 4
Finishing Thread [Child Thread]


JFRでの確認

image


テスト3 - 複数(3つ)のスレッドを作成


テストシナリオ3-1 - Runnable Interfaceを使って3つのChildスレッドを作成


コード


1) メインクラス


ThreadTestMultiThreadViaRunnableMain

public class ThreadTestMultiThreadViaRunnableMain {

public static void main(String[] args) {
new ThreadTestMultiThreadViaRunnableChild();
new ThreadTestMultiThreadViaRunnableChild();
new ThreadTestMultiThreadViaRunnableChild();

Thread th = Thread.currentThread();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());

try {
Thread.sleep(10000);
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}



2) Childスレッド(Runnable Interfaceをimplement)

public class ThreadTestMultiThreadViaRunnableChild implements Runnable {

Thread th;

public ThreadTestMultiThreadViaRunnableChild() {
th = new Thread(this);
th.start();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());
}

@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Child Thread : " + i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}


実行結果

Thread Info - Id:13, Name:Thread-2, Priority:5, State:RUNNABLE

Thread Info - Id:14, Name:Thread-3, Priority:5, State:RUNNABLE
Thread Info - Id:15, Name:Thread-4, Priority:5, State:RUNNABLE
Thread Info - Id:1, Name:main, Priority:5, State:RUNNABLE
Child Thread : 0
Child Thread : 0
......<中略>......
Child Thread : 4
Finishing Thread [main]
Finishing Thread [Thread-2]
Finishing Thread [Thread-4]
Finishing Thread [Thread-3]


JFRでの確認

名前を指定せずし、Childスレッドを作ると、以下のように「Thread-2」、「Thread-3」、「Thread-4」になる。

image


テストシナリオ3-2 - Joinを使ってChildスレッドが全部終了するまで待つ


コード


1) メインクラス


ThreadTestMultiThreadJoinMain

public class ThreadTestMultiThreadJoinMain {

public static void main(String[] args) {
ThreadTestMultiThreadJoinChild th1 = new ThreadTestMultiThreadJoinChild("1st Thread");
ThreadTestMultiThreadJoinChild th2 = new ThreadTestMultiThreadJoinChild("2nd Thread");
ThreadTestMultiThreadJoinChild th3 = new ThreadTestMultiThreadJoinChild("3rd Thread");

System.out.println("1st Thread Alive? : " + th1.th.isAlive());
System.out.println("2nd Thread Alive? : " + th2.th.isAlive());
System.out.println("3rd Thread Alive? : " + th3.th.isAlive());
try {
System.out.println("Waiting for child threads to be finished");
th1.th.join();
th2.th.join();
th3.th.join();
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.println("1st Thread Alive? : " + th1.th.isAlive());
System.out.println("2nd Thread Alive? : " + th2.th.isAlive());
System.out.println("3rd Thread Alive? : " + th3.th.isAlive());

System.out.printf("Finishing main thread");
}
}



2) Childスレッド


ThreadTestMultiThreadJoinChild

public class ThreadTestMultiThreadJoinChild implements Runnable {

String threadName;
Thread th;

public ThreadTestMultiThreadJoinChild(String threadName) {
this.threadName = threadName;
th = new Thread(this, threadName);
th.start();
System.out.printf("Thread Info - Id:%d, Name:%s, Priority:%d, State:%s%n",
th.getId(), th.getName(), th.getPriority(), th.getState());
}

@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Child Thread : " + i);
Thread.sleep(2000);
}
} catch (InterruptedException ie) {
System.out.println(ie);
}
System.out.printf("Finishing Thread [%s]%n", th.getName());
}
}


Thread Info - Id:13, Name:1st Thread, Priority:5, State:RUNNABLE

Thread Info - Id:14, Name:2nd Thread, Priority:5, State:RUNNABLE
Thread Info - Id:15, Name:3rd Thread, Priority:5, State:RUNNABLE
1st Thread Alive? : true
2nd Thread Alive? : true
3rd Thread Alive? : true
Waiting for child threads to be finished
Child Thread : 0
Child Thread : 0
......<中略>......
Child Thread : 4
Child Thread : 4
Finishing Thread [3rd Thread]
Finishing Thread [2nd Thread]
Finishing Thread [1st Thread]
1st Thread Alive? : false
2nd Thread Alive? : false
3rd Thread Alive? : false
Finishing main thread


JFRでの確認

image

mainスレッドはほとんど待ち状態(Java Monitor Wait)。mainスレッドの最後を拡大してみると、最後にJava Thread Endが見える。