第6章 スレッド
この章では、スレッドという概念を学ぶ。スレッドは応用範囲の広い概念である。例えば Java GUI アプリケーションの場合、スレッドを用いるとアニメーションを実現できる。また、スレッドはネットワークプログラミングをする際にも必須の概念である。
GUI アプリケーションの paintComponent などのメソッド、あるいは GUI 部品のコールバックメソッド（mouseClicked, keyPressed,actionPerformed など）はイベントによって呼び出される。これらのメソッドが実行されている間は、アプリケーションは他の仕事をすることができない。このため、これらのメソッドはすぐに実行を終える必要がある。アニメー
ションやゲームのように何らかの動きがあるアプリケーションは、mouseClicked や paintComponent メソッドにその処理を直接記述することはできない。何らかの方法でアプリケーションのイベント処理と同時に、これらの処理を行なわなければならない。
このようなコンピューターの処理を行なう単位を（thread, もともとの意味は“糸”）という ⦅具体的にいえば、 変数の値や、プログラムのどこを実行しているか、 どのようなメソッドのどこから呼び出されたかなどの情報（プログラムカウンターを含む CPU のレジスター、 およびスタックなどの情報）のことである。⦆。つまり、アニメーションやゲームの GUI アプリケーションはスレッ, mutli-thread）。CPU が 1 つしかないコドを複数必要とする（ンピューターでは、実際にはスレッドを短い時間で切り替えて実行し、並行に実行されているように見せかける。CPU が複数個あれば、スレッドを別々のCPU に割り当てて同時実行することも可能である。
関数呼出とスレッドの比較
関数呼出し

スレッド||+----+||+----+|||||+----+|
|
|
|
|
|
|
|

ここでは、Java で新しいスレッドを生成し実行するための方法を学ぶ。

6.1 スレッドの生成と実行スレッドを生成するためには、その新しいスレッドが実行するメソッドを指定しなくてはならない。そのメソッドの名前は Java では run という無引数・戻り値なしのメソッドとすることが決まっている。run は、Runnable インタフェース のメソッドである。

オブジェクト指向言語 第 6 章 p.1次のような簡単な例では MyRunnable クラスが Runnable インタフェースを実装している。つまり、run というメソッドを持っている。
run メソッドの中身は単純な出力の繰り返しで、繰り返しの途中でThread.sleep というメソッドをを呼んで 10 ミリ秒寝る（実行を止める）。
ファイル ThreadTest.java
1 class MyRunnable implements Runnable {2String name;3
MyRunnable(String n) {4name = n;5}
6public void run() {7int i;8for (i = 0; i < 10; i++) {9try {
10
Thread.sleep(10); // 10ミリ秒お休み
11} catch (InterruptedException e) {}12System.out.printf("%s: %d, ", name, i);
13}
14}
15 }
1617 public class ThreadTest {18
public static void main(String[] args) {19
Thread ta = new Thread(new MyRunnable("A"));
20
Thread tb = new Thread(new MyRunnable("B"));
21
Thread tc = new Thread(new MyRunnable("C"));22
ta.start();23
tb.start();24
tc.start();
25
}
26 }

ThreadTest クラスに main 関数があり、ここでスレッドを生成している。
Thread のコンストラクターの引数は Runnableを実装している必要がある。
new 演算子で Thread オブジェクトを生成してスレッドを作成（つまり実行を準備）し、この Thread オブジェクトの start メソッドを呼び出して、実際にスレッドの実行を開始する。
下は、このプログラムの実行例である。各スレッドが並行に実行されていることがわかる。（もちろんスレッドが切り替わるタイミングによって、この例と異なる出力になることもある。）A: 0, A: 1, B: 0, A: 2, A: 3, B: 1, A: 4, C: 0, A: 5, B: 2, A:
B: 3, A: 8, C: 1, A: 9, B: 4, B: 5, B: 6, C: 2, B: 7, C: 3, B:
B: 9, C: 5, C: 6, C: 7, C: 8, C: 9,6.2 スレッドを利用した GUI アプリケーション
例題 6.2.1 ぐるぐる廻る
単に文字列が円の上を動くだけの簡単なスレッドを利用した GUI アプリケーシ
ョンである。

オブジェクト指向言語 第 6 章 p.2

ファイル Guruguru.java
1 import java.awt.*;
2 import javax.swing.*;3
4 public class Guruguru extends JPanel implements Runnable
5private int r = 50;
6private volatile int x = 110, y = 70 ;7
private double theta = 0; // 角度
8
private volatile Thread thread = null;
910
public Guruguru() {11
setPreferredSize(new Dimension(200, 180));
12JButton startBtn = new JButton("start");13
startBtn.addActionListener(e -> startThread());
14
JButton stopBtn = new JButton("stop");15
stopBtn.addActionListener(e -> stopThread());
16
setLayout(new FlowLayout());
17
add(startBtn); add(stopBtn);
18
startThread();
19}
20
21
private void startThread() {22if (thread == null) {23thread = new Thread(this);24
thread.start();
25}
26}
27
28
private void stopThread() {29thread = null;
30}
31
32
@Override33
public void paintComponent(Graphics g) {34
super.paintComponent(g); // スーパークラスの paint35// 全体を背景色で塗りつぶす。
36
g.drawString("Hello, World!", x, y);37}

4 行めの implements Runnable に注意する。これで Guruguru クラスがrun という名前のメソッドを持っていることを宣言する。paintComponent メ
ソッドは座標 (x, y) に “Hello, World!”と表示するだけである。
このクラスは thread という Thread 型のフィールドを持っている。threadの初期値は null である。null は、 未生成のオブジェクトを表す値（C 言語のNULL に相当する）で、thread に最初は意味のある値が割り当てられていないことを示す。また、volatile /v'ɔlətˌail/（揮発性の、うつろいやすい、という意味）という修飾子は、スレッドがフィールドのキャッシュ（局所的なコピー）を作らないように指示する働きがある。これによってスレッドが（他のスレッドによって変更されたかもしれない）フィールドの最新の値を参照することを保証する。

オブジェクト指向言語 第 6 章 p.3

スレッドはこのアプリケーションでは startThread メソッド内で生成される。 Thread のコンストラクターの引数は、この場合は this — つまりGuruguruクラスのオブジェクト自身である。（実質的には、これは自身の runメソッドを指す。） thread を生成した後、この thread の start メソッドを起動してスレッドの実行をスタートしている。
stopThread メソッドは、スレッドの実行を止めるためのメソッドである。
次に、run メソッドは次のように定義されている。
ファイル Guruguru.java
3940414243444546474849505152 }public void run() {Thread thisThread = Thread.currentThread();for (; thread == thisThread; theta += 0.02) {x = 60 + (int)(r * Math.cos(theta));y = 100 - (int)(r * Math.sin(theta));repaint(); // paintComponent を間接的に呼出す
try {Thread.sleep(30); // 30 ミリ秒お休み} catch (InterruptedException e) {}}}public static void main(String[] args) { /* 省略 */Thread クラスのクラスメソッド currentThreadは、このメソッドを実行しているスレッドを返す。この時点では、フィールド thread と同じオブジェクトが返るはずである。
実際にスレッドが実行するメソッド （run メソッド）のループの条件式
thread == thisThread は奇妙に見えるが、stopThread メソッドによって、thread の値が null に変更されると、このループは終了し、スレッド自体も終了する。このような手法でスレッドを停止できるようにしておくのが一般的である。ループの中では、x と y の値を計算して再描画するとThread.sleep を呼んで 30 ミリ秒スリープする。Thread.sleep はInterruptedException という例外 を起こす可能性がある（その説明は割愛する）ので周りを try 〜 catch で囲んでいる。
GUI アプリケーションでスレッドを使うときには、通常run メソッドを定義する。メソッド内は、通常は Thread 型のフィールドの値を null に変更することによって、スレッドを停止できるようなループにしておく。
クラスに implements Runnable を付け加える。
Thread 型のフィールド （初期値 null）を追加する。
コンストラクターなどでスレッドを生成する。のようにする。

オブジェクト指向言語 第 6 章 p.4

Q 6.2.2 TextAnimation.java は、文字列が左から右に移動し、右端まで移動すれば、再び左端から現れるアニメーションを表示する。
TextAnimation.java を完成させよ。
ファイル TextAnimation.java
1 import java.awt.*;
2 import javax.swing.*;3
4 public class TextAnimation extends JPanel
5
private int x = 100;
6private volatile Thread thread = null;
7
8
public TextAnimation() {9setPreferredSize(new Dimension(200, 150));
10JButton startBtn = new JButton("start");11
startBtn.addActionListener(e -> startThread());
12
JButton stopBtn = new JButton("stop");13
stopBtn.addActionListener(e -> stopThread());
14
setLayout(new FlowLayout());
15
add(startBtn); add(stopBtn);
16
startThread();17}
18
19// startThread, stopThread は Guruguru と同じ
20
21@Override22
public void paintComponent(Graphics g) {23
super.paintComponent(g); // スーパークラスの paintCom
24// 全体を背景色で塗りつぶす。
25
g.drawString("Hello, World!", x, 100);
26}
27
28
public void run() {29Thread thisThread = Thread.currentThread();30
while () {31x += 5;
32
if (x > 200) { x = 0; }
33
34
try {
35
Thread.sleep(100); // 100 ミリ秒お休み36} catch (InterruptedException e) {}
37}
38}
39
40// main メソッドは省略
41 }Q 6.2.3 run メソッドをラムダ式として定義するように、Guruguru.java を書き換えよ。
ファイル Guruguru.java
1 import java.awt.*;

オブジェクト指向言語 第 6 章 p.5

2 import javax.swing.*;3
4 public class Guruguru{
5private int r = 50;
6private volatile int x = 110, y = 70;
7private double theta = 0; // 角度
8
private volatile Thread thread = null;
910… // コンストラクターは元のバージョンと同じ
1112
private void startThread() {13if (thread == null) {14thread = new Thread({15/* 元のバージョンの run メソッドの中身と同じ */16});
17thread.start();
18}
19}
20
21… // stopThread, paintComponent, main は元のバージョンと22 }問 6.2.4 （ビリヤード？）円が等速で斜めに動いて上下左右の壁にぶつかったとき、入射角と反射角が等しくなるように跳ね返るような GUI アプリケーションを書け。

6.3 SwingUtilities.invokeLater
このテキストで紹介している GUI ライブラリーの Swing は、シングルスレッドでアクセスするように設計されており、paintComponent やactionPerformed のようにイベントから起動されるメソッドのスレッドからGUI オブジェクトの状態を取得・変更しなければいけない、という制限がある。
そのため、別に生成したスレッドから Swing のメソッドを呼び出すときは、
SwingUtilities.invokeLater メソッドを使い、イベントキューに処理を投げ込んでおき、あとで Swing のスレッドに処理してもらう、という方法を取る。SwingUtilities.invokeLater メソッドには、 Runnable のオブジェクトを渡す。下のプログラムでは 36 行めで SwingUtilities.invokeLater
メソッドを使ってスレッドから GUI のラベルの文字列を変更している。
また、main メソッドのなかで Swing のメソッドを呼び出すときにSwingUtilities.invokeLater メソッドを使うのも同じ理由である。
ファイル Denko.java
1 import java.awt.*;
2 import javax.swing.*;3
4 public class Denko extends JPanel implements Runnable {5private JLabel label;
6
private volatile Thread thread = null;
7
private String msg = "0123456789ABCDEF ";89public Denko() {10label = new JLabel(msg);
11add(label);
12
JButton startBtn = new JButton("start");13
startBtn.addActionListener(e -> startThread());

オブジェクト指向言語 第 6 章 p.6

14
JButton stopBtn = new JButton("stop");15
stopBtn.addActionListener(e -> stopThread());
16
setLayout(new FlowLayout());
17add(startBtn); add(stopBtn);
18
startThread();
19}
20
21
private void startThread() {22if (thread == null) {23thread = new Thread(this);24
thread.start();
25}
26}
27
28
private void stopThread() {29thread = null;
30}
31
32
public void run() {33
Thread thisThread = Thread.currentThread();34
for (int i = 0; thread == thisThread; i = (i + 1) %35
String str = msg.substring(i) + msg.substring(0, i36
SwingUtilities.invokeLater(() -> label.setText(str37
try {38
Thread.sleep(100); // 100 ミリ秒お休み39} catch (InterruptedException e) {}
40}
41}
4243
public static void main(String[] args) {44
SwingUtilities.invokeLater(() -> {45JFrame frame = new JFrame("電光掲示板");46
frame.add(new Denko());
47
frame.pack();
48
frame.setVisible(true);
49
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOS50});
51}
52 }

なお、repaint メソッドは、SwingUtilities.invokeLater と同じようにイベントキューを利用するため、repaint() の呼出しは、SwingUtilities.invokeLater の中に入れる必要はない。例えば、
Guruguru クラスも repaint の呼出しの周りでSwingUtilities.invokeLater メソッドを使っていない。

6.4 スレッドを利用したプログラム
例題 6.4.1 ソーティングの視覚化
ファイル BubbleSort1.java（その 1）
1 import java.awt.*;
2 import javax.swing.*;

オブジェクト指向言語 第 6 章 p.7

34 public class BubbleSort1 extends JPanel implements Runna
5
private int[] data = new int[12];6private final Color[] cs = {7Color.RED, Color.ORANGE, Color.GREEN, Color.BLUE};
8
private volatile Thread thread = null;
9
private int i, j;
10
11
public BubbleSort1() {12setPreferredSize(new Dimension(320, 250));
13startThread();14}
1516
private void startThread() {17if (thread == null) {18thread = new Thread(this);
19thread.start();20}
21}

これはバブルソート (bubble sort) と呼ばれるアルゴリズムを視覚化したものである。
ファイル BubbleSort1.java（その 2）2324
25262728
2930
3132
33
34
35

@Override
public void paintComponent(Graphics g) {int k;
super.paintComponent(g);g.setColor(Color.YELLOW);
g.fillOval(5, 50 + j * 10, 10, 10);g.setColor(Color.CYAN);
g.fillOval(5, 50 + i * 10, 10, 10);for (k = 0; k < data.length; k++) {g.setColor(cs[k % cs.length]);g.fillRect(20, 50 + k * 10, data[k] * 5, 10);}}

paintComponent も棒グラフ（第 3 章の Graph.java）の時とほとんど同じである。
配列を乱数で初期化するメソッドを用意しておく。
ファイル BubbleSort1.java（その 3）
37
38
39
40
41
42

private void prepareRandomData() {int len = data.length;
for (int k = 0; k < len; k++) {data[k] = (int)(Math.random() * len * 4);
}
}

// 適当

run メソッドの中は単なるバブルソートアルゴリズムだが、データのスワップをした後、再描画して少し止まるようになっている。
ファイル BubbleSort1.java（その 4）

オブジェクト指向言語 第 6 章 p.844
public void run() {45
while (true) {46
prepareRandomData();47// バブルソートアルゴリズム
48
for (i = 0; i < data.length - 1; i++) {49
for (j = data.length - 1; j > i; j--) {50
if (data[j - 1] > data[j]) { // スワップする。
51
int tmp = data[j - 1];
52
data[j - 1] = data[j];53data[j] = tmp;
54
repaint();55
try { // repaintの後でしばらく止まる
56
Thread.sleep(500);
57} catch (InterruptedException e) {}
58}
59}
60}
61
try { // 並び換え完了後に長めに止まる
62Thread.sleep(5000);63} catch (InterruptedException e) {}
64}
65}
66
67
public static void main(String[] args) { /* 省略 */ }68 }問 6.4.2 クイックソート (quick sort) アルゴリズムを BubbleSort.java にならって、データのスワップをしたあと、再描画して少し止まるようにアニメーション化せよ。
参考: クイックソート
12
3
4
5
6
7
8
9101112
13
1415
16
1718
1920
2122

private int[] data = …
private void swap(int i, int j) {int tmp = data[i];data[i] = data[j];data[j] = tmp;}
private void qsort(int left, int right) {if (left >= right) return;int i = left, j = right;
int pivot = data[i + (j - i) / 2];
while (true) {while (data[i] < pivot) i++;while (pivot < data[j]) j--;if (i >= j) break;swap(i, j);i++; j--;}qsort(left, i - 1);
qsort(j + 1, right);}

オブジェクト指向言語 第 6 章 p.9

例題 6.4.3 ソーティングの視覚化 （その 2）
BubbleSort1.java では、スレッドはいわば自分で目覚しを仕掛けて起きていたが、他人（他のスレッド）に起こしてもらうことを期待して寝ることもできる。 次のプログラムではボタンを押した時にスレッドが再開されるようになっている。
ファイル BubbleSort2.java（その 1）
1 import javax.swing.*;
23 import java.awt.*;
4 import java.awt.event.*;
5
6 public class BubbleSort2 extends JPanel
7implements Runnable, ActionList
8/* フィールドは、ほとんど BubbleSort1 と同じ */

1415
16
1718
19
20
21
22
23
24

private volatile boolean threadSuspended = true; /* 追public BubbleSort2() {setPreferredSize(new Dimension(320, 250));
JButton step = new JButton("Step");
step.addActionListener(this);
setLayout(new FlowLayout());
add(step);
startThread();}/* startTread は BubbleSort1 と同じ */このクラスは Runnable と ActionListener の 2 つのインタフェースを実装しているので、implements のあとに 2 つのインタフェース名を「,」（コンマ）で区切って並べている。
目覚しを仕掛けずに寝るには sleep の代わりに、以下のような、メソッド を用いた形を使う。
ファイル BubbleSort2.java（その 2）32333435363738394041424344454647484950515253

public void run() {while(true) {prepareRandomData();// バブルソートアルゴリズム
for (i = 0; i < args.length - 1; i++) {for (j = args.length - 1; j > i; j--) {if (args[j - 1] > args[j]) { // スワップする。
int tmp = args[j - 1];
args[j - 1] = args[j];args[j] = tmp;}
repaint();/* repaint の後で止まる */try {synchronized (this) {while (threadSuspended) {wait();}
threadSuspended = true;}} catch (InterruptedException e) {}}オブジェクト指向言語 第 6 章 p.10

545556}}}

また synchronized というキーワードにも注意して欲しい。 synchronizedは排他制御（後述）を行なうための構文である。
また、スレッドを起こすには、というメソッド を使う。

ファイル BubbleSort2.java（その 3）
58
59
60
6162

public synchronized void actionPerformed(ActionEvent e
threadSuspended = false;
notify();}/* paintComponent, main などは BubbleSort1 と同じ */94}
95 }
この例のようにメソッドの定義の最初に synchronized を修飾子として付け加えると、次のようにメソッドの本体全体を synchronized (this) { … } で囲うのと同じことになる。
123456

public void actionPerformed(ActionEvent e) {synchronized (this) {threadSuspended = false;notify();}}

この例では、actionPerformed の中で notify を呼んで、スレッドの実行を再開させている。threadSuspended という変数の値を変更しているのは、この actionPerformed 以外からも notify が（隠れて）呼び出される場合があり、その時にスレッドが間違って起きないようにするためである。

BubbleSort2.java の場合は、少し工夫をすれば、スレッドを使わなくても同じような動作をするプログラムを作ることができる。しかし、 Quick Sort の場

オブジェクト指向言語 第 6 章 p.11

合は、再帰呼出しを使っているので、スレッドを使わずに同じような動作をするプログラムを書くのは難しい。一般的には、このようにアニメーションではないプログラムに対しても、スレッドは有効なテクニックである。
問 6.4.4 クイックソートも同じようにボタンを押すと 1 ステップ動く（一回の比較が起こる）ように改造せよ。
問 6.4.5 （発展）同じデータに対するクイックソートとバブルソートを、比較できるように横に並べて表示するようにせよ。
問 6.4.6 （発展）スレッドを使わずに BubbleSort2.java と同じように、ボタンをクリックすると 1 ステップ動くプログラムを作成せよ。
問 6.4.7 （挑戦）スレッドを使わずに、クイックソートでボタンをクリックすると 1 ステップ動くプログラムを作成せよ。

6.5 synchronized 文
synchronized 文 は次のような形で用いる。
synchronized (式) ブロック“式”はオブジェクトである（つまり整数などのプリミティブ型ではない）必要がある。synchronized 文はこのオブジェクトを“鍵”として、ブロックを排他実行する。つまり、同じ鍵を持つ synchronized 文は複数存在している可能性もあって、 synchronized ブロックを実行している間、他のスレッドに同じ鍵を用いている synchronized 文のブロックの実行を待たせる。ブロックの中でwait メソッドなどを呼んだ場合は、鍵は一旦返却され、他のスレッドが同じ鍵を用いている synchronized 文のブロックを実行することができる。
synchronized 文は、途中で中断されると変なことが起こりうる一連の文を実行する時に必要になる。例えば、いくつかのスレッドで共通の変数 xを増分するために次のような単純な文:x = x + 1;
を実行するような場合も、synchronized が必要になる。
synchronizedがない場合に起こりうること:
1. 最初 x = 0 とする。
2. スレッド A が x + 1 の値 (1) を計算する。
3. ここでスレッドが切り替わる。
4. スレッド B が x + 1 の値 (1) を計算する。
5. スレッド B が x に 1 を代入する。
6. ここでスレッドが切り替わる。
7. スレッド A が x に 1 を代入する。
つまり、x = x + 1; という文が 2 回実行されたにも関わらず、 x の値は 1 しか増えていない。これは、x + 1 の値の計算と x への代入の間にスレッドの切り替わりが起こったためである。 synchronized を使うとこのような事態を避けることができる。いくつかのスレッドで共通の変数をアクセスする時は、大オブジェクト指向言語 第 6 章 p.12

抵このような synchronized 文が必要になる。特に、wait と notify の呼出しにはそのまわりに synchronized が必要である。

6.6 問: ハノイの塔
問 6.6.1 ハノイの塔のアルゴリズムをアニメーション化せよ。

（ヒント） ハノイの塔のルール:
3 つの棒と直径が 1, 2, …, n の n 枚の真中に穴のあいた円盤を用いる。まず、すべての円盤が、小さいものを上に大きさの順に 1 つの棒にささっている。すべての円盤を別の一つの棒に移動できたら終了である。ただし、
1. 一度に 1 枚の円盤だけを動かすことができる、
2. 小さな円盤の上に大きな円盤をのせてはいけない、という制限がある。
ハノイの塔は再帰法を使って解くことができる。つまり、n - 1 枚の場合の解き方がわかっているとして、n 枚を棒 A から棒 B へ移動する場合:
1. n - 1 枚の円盤を棒 A から棒 C へ移動する。このやり方はわかっている。
2. 一番下のもっとも大きな 1 枚を棒 A から棒 B へ移動する。
3. n - 1 枚の円盤を再び棒 C から棒 B へ移動する。というように考える。
すると単に output（TextArea のインスタンス）に手順を出力するメソッドの場合は以下のようになる。
123456
7

void hanoi(int n, String a, String b, String c) {if (n > 0) {hanoi(n - 1, a, c, b);
output.append("円盤 " + n + "を、" + a + "から" + bhanoi(n - 1, c, b, a);}}

オブジェクト指向言語 第 6 章 p.13

人間がこのような手順を間違えずに実行することは難しいが、コンピューターはまず間違えずに実行してくれる。
キーワード
スレッド、マルチスレッド、Runnable インタフェース、 volatile、null、Thread.sleep メソッド、 wait メソッド、notify メソッド、
synchronized

オブジェクト指向言語 第 6 章 p.14