Java超|スレッドを作る2つの方法

スレッドの作り方は1つだけではない。Thread を受け継ぐ道と、Runnable で任務内容を渡す道を知ると、Javaのクラス設計はもっと柔軟になります。

これまでのスレッド学習では、Thread クラスを継承して run メソッドを定義し、start メソッドで起動する方法を見てきました。

この方法は、とても分かりやすいです。

Thread を継承したクラスは、スレッドとして動くための機能を持ちます。
その中の run メソッドに、別スレッドで実行したい処理を書きます。
そして、作成したオブジェクトに start を呼び出すと、新しい処理の流れが始まります。

たとえば、ドラゴンボール風にたとえるなら、戦士クラスそのものが「別任務へ出られる戦士」として作られているようなものです。

class WarriorTask extends Thread
{
    public void run()
    {
        別スレッドで行いたい処理
    }
}

この形では、WarriorTask 自身が Thread の力を受け継いでいます。

そのため、WarriorTask のオブジェクトに対して start を呼び出せます。

しかし、Javaではいつもこの方法だけで十分とは限りません。

なぜなら、Javaのクラスは 1つのクラスしか継承できない からです。

たとえば、あるクラスがすでに別の親クラスを継承している場合、さらに Thread クラスを継承することはできません。

ドラゴンボール風に言えば、ある戦士がすでに「記録係の家系」や「支援部隊の流派」を受け継いでいる場合、さらに Thread という別の親クラスまで同時に受け継ぐことはできない、というイメージです。

けれども、別任務で動く方法は1つだけではありません。

Thread を親クラスとして受け継がなくても、「別スレッドで動く任務内容」を持たせる方法があります。

それが Runnable インターフェイスを実装する方法 です。

Javaでスレッドを作る主な方法は、次の2つです。

方法基本形特徴
Thread クラスを継承するextends Threadスレッド用クラスを直接作るので分かりやすい
Runnable インターフェイスを実装するimplements Runnableほかのクラス継承と両立しやすい

この記事では、なぜスレッドの作り方が2つあるのか、Runnable を使うと何が変わるのか、Sample6.java を使って、ドラゴンボールの世界観でやわらかく整理していきます。

これまでの方法は Thread クラスを継承するやり方

まず、これまで使ってきた Thread 継承の形を整理しておきましょう。

Thread クラスを継承する場合、基本形は次のようになります。

class クラス名 extends Thread
{
    public void run()
    {
        別スレッドで行いたい処理
    }
}

この形では、クラスそのものが Thread の機能を受け継ぎます。

そのため、作成したオブジェクトに対して start を呼び出せます。

WarriorTask task1 = new WarriorTask();
task1.start();

この書き方は、スレッドの基本を学ぶ段階ではとても理解しやすいです。

要素役割
extends Threadスレッドとして動けるクラスにする
run別スレッドで行う処理を書く
start新しいスレッドを起動する

ドラゴンボール風に言えば、WarriorTask は「最初から別任務へ出られる戦士クラス」です。

戦士オブジェクトを作り、start で出撃命令を出せば、run に書いた任務内容が別の流れで動き始めます。

Thread 継承の分かりやすさ

Thread 継承のよいところは、流れがまっすぐなことです。

手順内容
1Thread を継承したクラスを作る
2run に処理を書く
3オブジェクトを作る
4start を呼ぶ
5run が別スレッドで動く

クラス自身がスレッドなので、考え方がシンプルです。

class WarriorTask extends Thread
{
    public void run()
    {
        System.out.println("戦士が任務を進めています。");
    }
}

このように書けば、WarriorTask のオブジェクトは start を使って起動できます。

ドラゴンボール風に言うと、WarriorTask は「任務に出る力」を最初から持った戦士です。

だから、任務内容を書いた run を持たせて、start で動かせばよいわけです。

でも Thread クラスを継承できない場面がある

ここで問題になるのが、Javaの継承ルールです。

Javaでは、1つのクラスが同時に複数のクラスを継承することはできません。

状況Javaでできるか
1つのクラスだけを継承するできる
2つ以上のクラスを同時に継承するできない

たとえば、次のようなクラスがあるとします。

class TrainingRecordBase
{
}

そして、修行記録を扱う戦士クラスを TrainingRecordBase から継承して作りたいとします。

class TrainingRecorder extends TrainingRecordBase
{
}

この TrainingRecorder は、すでに TrainingRecordBase を継承しています。

この状態で、さらに Thread も継承することはできません。

class TrainingRecorder extends TrainingRecordBase, Thread
{
}

このような複数クラスの同時継承は、Javaではできません。

ドラゴンボール風にたとえると、GohanAnalyzer がすでに「記録解析部隊の親クラス」を受け継いでいる場合、さらに Thread という親クラスまで同時に受け継ぐことはできない、という感じです。

けれども、GohanAnalyzer が別任務で動くための「任務内容」を身につけることはできます。

そこで Runnable が登場します。

Runnable インターフェイスを使う方法

Runnable は、別スレッドで動かしたい処理内容を表すためのインターフェイスです。

Runnable を実装するクラスでは、run メソッドを定義します。

基本形は次のようになります。

class クラス名 implements Runnable
{
    public void run()
    {
        別スレッドで行いたい処理
    }
}

Thread 継承と似ていますが、宣言が違います。

方法クラス宣言
Thread 継承class クラス名 extends Thread
Runnable 実装class クラス名 implements Runnable

Runnable を実装したクラスは、Thread クラスを継承していません。

そのため、別のクラスを継承しながら、Runnable を実装するという設計がしやすくなります。

ドラゴンボール風に言えば、Thread 継承は「戦士クラスそのものが別任務用の力を親クラスから受け継ぐ」方法です。

一方、Runnable 実装は「戦士が別任務で行う任務内容を持つ」方法です。

Thread を親クラスとして受け継がなくても、Runnable という約束を守り、run に任務内容を書けば、あとで Thread オブジェクトに渡して動かせます。

図:Thread 継承と Runnable 実装の2つの道

この図が示していること

この図は、Javaでスレッドを作る2つの方法を比較しています。

左側は Thread クラスを継承する方法です。
クラス自身が Thread の機能を持つため、そのオブジェクトに start を呼び出せます。

右側は Runnable インターフェイスを実装する方法です。
Runnable 実装クラスは、別スレッドで動かす処理内容を持ちます。
そのオブジェクトを Thread オブジェクトに渡し、Thread 側に start を呼び出して起動します。

図の要素意味
extends ThreadThread クラスを継承してスレッド用クラスを作る
implements RunnableRunnable を実装して処理内容を作る
runどちらの方法でも必要な処理本体
start新しいスレッドを起動する
多重継承できないRunnable が必要になる大きな理由

この図から分かることは、スレッドを作る方法は1つではなく、クラス設計に合わせて選べるということです。

Runnable でも run メソッドを書く

Runnable を使う場合でも、run メソッドを書く点は変わりません。

Thread 継承でも Runnable 実装でも、別スレッドで行いたい処理は run に書きます。

方法run は必要か
Thread 継承必要
Runnable 実装必要

違うのは、start をどこに呼ぶかです。

Thread 継承では、そのオブジェクト自身が Thread なので、自分自身に start を呼べます。

WarriorTask task1 = new WarriorTask("GokuWarrior");
task1.start();

Runnable 実装では、Runnable オブジェクト自身には start はありません。

そのため、Thread オブジェクトを別に作る必要があります。

WarriorTask task1 = new WarriorTask("GokuWarrior");

Thread th1 = new Thread(task1);
th1.start();

この違いを押さえることが、Runnable 方式の理解ではとても大切です。

Runnable 実装クラスは処理内容を持つ

Runnable を実装したクラスのオブジェクトは、「別スレッドで何をするか」を持っています。

しかし、そのオブジェクト自身がスレッドとして起動するわけではありません。

オブジェクト役割
Runnable 実装オブジェクト別スレッドで行う処理内容を持つ
Thread オブジェクト実際に新しいスレッドを起動する

ドラゴンボール風にたとえると、Runnable 実装オブジェクトは「任務内容が書かれた指令札」を持つ戦士です。

しかし、その指令札だけでは任務は始まりません。

その任務内容を Thread という出撃役に渡し、Thread に start を呼び出すことで、実際に任務が始まります。

Runnable 方式の起動手順

Runnable 方式では、次の3段階でスレッドを起動します。

手順内容
1Runnable を実装したクラスのオブジェクトを作る
2そのオブジェクトを引数にして Thread オブジェクトを作る
3Thread オブジェクトに start を呼ぶ

コードで見ると、次のようになります。

WarriorTask task1 = new WarriorTask("GokuWarrior");

Thread th1 = new Thread(task1);
th1.start();

この3行には、それぞれ役割があります。

コード役割
new WarriorTask("GokuWarrior")別スレッドで動かす処理内容を持つオブジェクトを作る
new Thread(task1)task1 の run を動かすための Thread オブジェクトを作る
th1.start()新しいスレッドを起動する

ここで start を呼ぶ相手は task1 ではありません。

start を呼ぶのは th1 です。

th1.start();

Runnable 方式では、この点を間違えないようにすることが大切です。

図:Runnable 方式でスレッドを起動する流れ

この図が示していること

この図は、Runnable 方式でスレッドを起動する流れを表しています。

Runnable を実装した WarriorTask は、run に処理内容を持ちます。

そのオブジェクト task1 を作っただけでは、まだスレッドは始まりません。

task1 を Thread オブジェクトに渡し、th1.start() を呼ぶことで、task1 の run が別スレッドで動き始めます。

図の要素意味
implements Runnable別スレッドで動かす処理内容を持つ
task1Runnable 実装オブジェクト
new Thread(task1)task1 を動かす Thread オブジェクトを作る
th1.start()新しいスレッドを起動する
task1 の run別スレッドで実行される処理

この図から分かることは、Runnable 方式では「処理内容」と「スレッドとして起動する役」が分かれているということです。

Runnable 実装を確認する

ここでは、Runnable インターフェイスを実装する形でスレッドを作ります。

GokuWarrior は、別スレッドで修行手順を確認します。
main 側では、VegetaWarrior が作戦の流れを整理します。

ファイル名:Sample6.java

class WarriorTask implements Runnable
{
    private String name;

    public WarriorTask(String nm)
    {
        name = nm;
    }

    public void run()
    {
        // 別スレッドで進める修行の流れ
        for(int i = 0; i < 5; i++) {
            System.out.println(name + "が修行の手順を確認しています。");
        }
    }
}

class Sample6
{
    public static void main(String[] args)
    {
        WarriorTask task1 = new WarriorTask("GokuWarrior");

        Thread th1 = new Thread(task1);
        th1.start();

        for(int i = 0; i < 5; i++) {
            System.out.println("VegetaWarriorが作戦の流れを整理しています。");
        }
    }
}

Sample6.java の全体像

このプログラムでは、WarriorTask クラスが Runnable を実装しています。

class WarriorTask implements Runnable

この宣言によって、WarriorTask は Runnable の約束に従うクラスになります。

Runnable を実装するクラスでは、run メソッドを定義します。

public void run()
{
    for(int i = 0; i < 5; i++) {
        System.out.println(name + "が修行の手順を確認しています。");
    }
}

この run に、別スレッドで行いたい処理を書いています。

ここでは、GokuWarrior が修行の手順を確認しているメッセージを5回表示します。

WarriorTask は Thread を継承していない

重要なのは、WarriorTask が Thread を継承していないことです。

class WarriorTask implements Runnable

このクラスは、extends Thread ではありません。

そのため、WarriorTask のオブジェクト自身に start を呼ぶことはできません。

WarriorTask task1 = new WarriorTask("GokuWarrior");

この task1 は、別スレッドで動かす処理内容を持っています。

しかし、task1 自身が Thread オブジェクトではありません。

オブジェクト内容
task1Runnable 実装オブジェクト
持っているものrun に書かれた処理内容
持っていないものstart で起動する機能

そのため、次に Thread オブジェクトを作ります。

Thread th1 = new Thread(task1);

このコードにより、task1 の run を別スレッドで動かす準備ができます。

Thread オブジェクトを作って start する

Runnable 方式では、Thread オブジェクトを別に作ります。

Thread th1 = new Thread(task1);

ここで task1 を Thread のコンストラクタに渡しています。

これは、

この task1 の run を、新しいスレッドで動かす準備をしてください

という意味です。

そして、start を呼び出します。

th1.start();

ここで新しいスレッドが起動します。

このとき動くのは、task1 の run です。

コード意味
WarriorTask task1 = new WarriorTask("GokuWarrior");GokuWarrior の任務内容を持つオブジェクトを作る
Thread th1 = new Thread(task1);task1 を動かすための Thread を作る
th1.start();新しいスレッドを起動する
task1 の run別スレッドで実行される

ドラゴンボール風に言えば、GokuWarrior は「修行手順を確認する」という任務内容を持っています。

ただし、その任務札だけでは出撃できません。

Thread という出撃役に GokuWarrior の任務内容を渡し、th1.start() で任務開始の合図を出すことで、GokuWarrior の run が別の流れで動き出します。

main 側の処理も進む

th1.start() を呼び出したあと、main 側も自分の処理を続けます。

for(int i = 0; i < 5; i++) {
    System.out.println("VegetaWarriorが作戦の流れを整理しています。");
}

このため、プログラムには2つの流れができます。

流れ内容
th1 のスレッドGokuWarrior が修行の手順を確認する
main スレッドVegetaWarrior が作戦の流れを整理する

この2つは並行して進むため、表示順は固定されません。

実行すると、たとえば次のように表示されることがあります。

GokuWarriorが修行の手順を確認しています。
VegetaWarriorが作戦の流れを整理しています。
GokuWarriorが修行の手順を確認しています。
VegetaWarriorが作戦の流れを整理しています。
VegetaWarriorが作戦の流れを整理しています。
GokuWarriorが修行の手順を確認しています。

別の実行では、VegetaWarrior の表示が先にまとまって出ることもあります。
GokuWarrior の表示が先に進むこともあります。

これは、スレッドの動きとして自然です。

作り方が Runnable 方式になっても、start をきっかけに新しい処理の流れが生まれる点は、Thread 継承と同じです。

Thread 継承と Runnable 実装の違い

ここで、2つの方法を比較して整理します。

項目Thread 継承Runnable 実装
クラス宣言extends Threadimplements Runnable
run の定義必要必要
start の呼び出し先作成したオブジェクト自身Thread オブジェクト
Thread オブジェクト作成自分自身が Thread別に new Thread(task) が必要
分かりやすさシンプル手順が少し増える
柔軟性ほかのクラス継承と両立しにくいほかのクラス継承と両立しやすい

Thread 継承では、クラスそのものがスレッドです。

Runnable 実装では、クラスは「スレッドで行う処理内容」を持ち、実際の起動は Thread オブジェクトに任せます。

この違いが、設計上とても大切です。

Runnable の方法が柔軟な理由

Runnable 方式が柔軟なのは、役割が分かれているからです。

役割Runnable 方式での担当
何をするかRunnable 実装クラス
どう起動するかThread オブジェクト

Runnable 実装クラスは、run の中に処理内容を書きます。

Thread オブジェクトは、その処理内容を新しいスレッドとして動かします。

つまり、Runnable 方式では、

処理内容と起動役を分ける

という設計になります。

ドラゴンボール風に言えば、GokuWarrior は「修行手順を確認する」という任務内容を持っています。

一方、Thread は、その任務内容を実際に別任務として動かす出撃役です。

任務内容と出撃役が分かれているので、クラス設計が柔軟になります。

すでに別クラスを継承している場合にも使いやすい

Runnable の大きな利点は、すでに別のクラスを継承している場合でも使いやすいことです。

Javaではクラスを2つ同時に継承できません。

しかし、インターフェイスは実装できます。

たとえば、次のような考え方ができます。

class TrainingRecorder extends RecordBase implements Runnable
{
    public void run()
    {
        修行記録を整理する処理
    }
}

このように、RecordBase を継承しながら、Runnable を実装できます。

要素意味
extends RecordBase記録係としての性質を受け継ぐ
implements Runnable別スレッドで動かす処理内容を持つ
run別スレッドで行う処理

ドラゴンボール風に言えば、GohanAnalyzer が「記録解析部隊の性質」を受け継ぎながら、「別任務で動くための任務内容」も持てるということです。

Thread クラスそのものを継承できなくても、Runnable を使えば、スレッドで動かす処理を組み込めます。

図:Runnable は処理内容と起動役を分ける

この図が示していること

この図は、Runnable 方式の役割分担を表しています。

Runnable 実装クラスは、run に処理内容を書きます。

Thread オブジェクトは、その Runnable 実装オブジェクトを受け取り、start によって新しいスレッドを起動します。

図の要素意味
Runnable 実装クラス何をするかを持つ
Thread オブジェクトどう起動するかを担当する
new Thread(task)処理内容を Thread に渡す
th.start()新しいスレッドを起動する
task の run新しい流れで実行される

この図から分かることは、Runnable 方式では、クラスが直接 Thread になるのではなく、処理内容を Thread に渡して動かすということです。

どちらの方法を使えばよいのか

学習の最初では、Thread 継承の方法が分かりやすいです。

クラスそのものがスレッドになるので、start までの流れが直感的です。

ただし、実際のクラス設計では、Runnable 方式のほうが使いやすい場面も多くあります。

場面向いている方法
スレッドの基本を理解したいThread 継承
すでに別のクラスを継承しているRunnable 実装
処理内容と起動役を分けたいRunnable 実装
設計を柔軟にしたいRunnable 実装

つまり、Thread 継承は「基本をつかみやすい方法」です。

Runnable 実装は「設計を柔軟にしやすい方法」です。

どちらが完全に正しい、という話ではありません。

状況に応じて選べることが大切です。

Runnable 方式で間違えやすいところ

Runnable 方式では、次の点に注意が必要です。

間違えやすい点正しい理解
Runnable 実装オブジェクトに start を呼ぶstart は Thread オブジェクトに呼ぶ
task1 を作っただけでスレッドが動くtask1 を Thread に渡し、start が必要
Runnable では run が不要Runnable でも run を定義する
Thread 継承とまったく違う動きになるどちらも最終的には別スレッドで run が動く

特に大切なのは、start の呼び出し先です。

Runnable 方式では、次のように書きます。

Thread th1 = new Thread(task1);
th1.start();

task1.start() ではありません。

なぜなら、task1 は Thread オブジェクトではなく、Runnable 実装オブジェクトだからです。

ドラゴンボール風に言えば、任務内容を書いた指令札そのものに「出撃せよ」と言っても動きません。

その指令札を出撃役である Thread に渡し、Thread に対して start を命じる必要があります。

Thread 継承と Runnable 実装の共通点

2つの方法には違いがありますが、共通点もあります。

共通点内容
run を定義する別スレッドで行う処理を書く
start で起動する新しい処理の流れを作る
main と並行して動くmain の処理も続く
表示順は固定されない複数の流れが動くため順番は変わる

作り方が違っても、スレッドとして動く本質は同じです。

新しいスレッドが起動し、run の中の処理が main とは別の流れで実行されます。

Sample6.java でも、GokuWarrior の run と、main 側の VegetaWarrior の処理が並行して進みます。

この点は、Thread 継承で作った場合と同じです。

ドラゴンボール風に整理する

今回の内容をドラゴンボール風に整理すると、次のようになります。

Javaドラゴンボール風のイメージ
Thread 継承戦士クラスそのものが別任務に出られる力を受け継ぐ
Runnable 実装戦士が別任務で行う任務内容を持つ
run任務内容を書いた作戦書
Thread オブジェクト実際に出撃させる役
new Thread(task1)任務内容を出撃役へ渡す
start出撃命令
Javaの単一継承親クラスとして受け継げるクラスは1つだけ
Runnable の柔軟性ほかの性質を受け継ぎながら別任務内容も持てる

Thread 継承は、スレッドの基本をつかみやすい方法です。

Runnable 実装は、クラス設計を柔軟にしやすい方法です。

どちらの方法でも、run に別スレッドで行いたい処理を書き、start によって新しい処理の流れを起動します。

ただし、Runnable 方式では、Runnable 実装オブジェクト自身に start を呼ぶのではなく、Thread オブジェクトを作って、その Thread に start を呼ぶ点が重要です。

スレッドを作る2つの方法で押さえておきたいこと

スレッドを作る2つの方法を学ぶときは、次の点を押さえると理解しやすくなります。

ポイント内容
スレッド作成方法は2つあるThread 継承と Runnable 実装
Thread 継承クラス自身がスレッドとして動ける
Runnable 実装別スレッドで動かす処理内容を持つ
Javaは多重継承できない複数のクラスを同時に継承できない
Runnable は柔軟ほかのクラス継承と組み合わせやすい
どちらも run が必要別スレッドで行う処理を書く場所
Runnable 方式では Thread が必要new Thread(task1) の形で起動役を作る
start は Thread に呼ぶRunnable 実装オブジェクト自身には start しない
動きの本質は同じどちらも新しい処理の流れを作る

スレッドを作る方法は1つではありません。

Thread クラスを継承する方法は、分かりやすくスレッドの基本をつかみやすい方法です。

Runnable インターフェイスを実装する方法は、処理内容と起動役を分けられるため、クラス設計を柔軟にしやすい方法です。

この2つの違いが分かると、スレッドは単なる書き方の暗記ではなく、クラス設計と処理の流れをどう組み立てるかという視点で理解できるようになります。