Java入門|スレッドの2つの作成方法

悟空の戦い方がひとつではないように、スレッドの作り方にも2つの道がある。

これまでスレッドを学ぶ中で、Threadクラスを継承して run() を定義し、start() で起動する方法を見てきました。
この方法はとてもわかりやすく、スレッドの基本をつかむにはぴったりです。

ただ、Javaのプログラムでは、いつもその方法だけで十分とは限りません。
クラスによっては、すでに別のクラスを継承していて、さらに Threadクラスまで継承したいという場面が出てきます。
けれどもJavaでは、クラスを2つ同時に継承することはできません。

ドラゴンボールの世界でたとえるなら、ある戦士がすでに特定の流派の修行を受け継いでいるのに、さらに別の流派の血筋そのものまで同時に受け継ぐことはできない、という感じです。
でも、戦い方を身につける方法は1つではありません。
血筋として受け継ぐ方法だけでなく、技の型を学んで実践する方法もあります。

Javaのスレッドも同じです。
スレッドを作るには、

  • Threadクラスを継承する方法
  • Runnableインターフェイスを実装する方法

という2つの道があります。

ここでは、なぜ2つ目の方法が必要になるのか、Runnableインターフェイスを使うとどんな形でスレッドを作るのかを、ドラゴンボールの世界観に置き換えながら、やさしく整理していきます。

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

まず、これまで使ってきた方法を整理しておきましょう。
今までのスレッド作成は、Threadクラスを継承したクラスを作り、その中に run() を書く形でした。

イメージとしては次のような形です。

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

このやり方のよいところは、とてもまっすぐでわかりやすいことです。
スレッドとして動くクラスをそのまま作るので、初心者にも理解しやすい形です。

ドラゴンボール風にいえば、戦士そのものが最初から「戦闘用の型」を受け継いで生まれてくるようなイメージです。
そのまま start() を呼び出せば、別の流れで動き始めます。

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

ここでひとつ問題が出てきます。
Javaでは、クラスの多重継承ができません。

つまり、あるクラスがすでに別のクラスを継承しているなら、さらに Threadクラスまで継承することはできないのです。

たとえば、ドラゴンボールの世界観で考えて、戦士クラスがすでに「修行生クラス」を継承していたとします。
その状態でさらに Threadクラスまで継承しようとしても、Javaのルール上それはできません。

表にするとこうです。

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

このルールがあるため、スレッドを作る方法が Threadクラス継承だけだと困ることがあります。
そこで登場するのが、Runnableインターフェイスを実装する方法です。

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

Runnableは、スレッドで動かしたい処理の内容を表すためのインターフェイスです。
これを実装すると、別スレッドで実行したい処理を run() に書けるようになります。

ここでのポイントは、Runnableを実装するクラスは Threadクラスを継承していない、ということです。
そのため、ほかのクラスを継承していても使いやすいのです。

ドラゴンボール風にいうと、これは戦士が特定の血筋そのものを継ぐのではなく、特定の戦闘スタイルを身につける約束をするようなものです。
その約束が Runnable であり、run() を実装することで「別スレッドで動く内容」を定義します。

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

ここで、2つの方法を並べて整理しておきましょう。

方法特徴
Threadクラスを継承する直接スレッド用クラスを作る。わかりやすい
Runnableインターフェイスを実装するほかのクラスを継承していても使いやすい

つまり、スレッドを作る道は1本ではありません。
プログラムの設計に合わせて、より柔軟な方法を選べるようになっているわけです。

Runnableインターフェイスを図でイメージする

この図が示していること

この図では、SaiyanTaskクラスが Threadクラスを継承するのではなく、Runnableインターフェイスを実装していることを表しています。

ここから分かるのは、スレッドで動かす処理は、必ずしも Threadクラスのサブクラスとして作らなくてもよい、ということです。
Runnable を実装すれば、run() に処理を書き、その内容をあとから Threadオブジェクトに渡して動かせます。

Runnableを実装したクラスでも run() を定義する

Runnable を使う場合でも、大事な点は変わりません。
やはり run() メソッドを定義して、その中に別スレッドで行いたい処理を書きます。

つまり、

  • Thread継承でも run() を書く
  • Runnable実装でも run() を書く

という共通点があります。

違いは、どうやって start() まで持っていくかです。
Thread継承なら、そのオブジェクト自身に start() を呼べました。
Runnable実装では、それだけではまだスレッドとして起動できないので、もうひと手順必要になります。

Runnable実装では Threadオブジェクトを作る必要がある

Runnable を実装したクラスのオブジェクトは、あくまで「動かしたい処理の中身」を持っているだけです。
それ自体が Threadオブジェクトではありません。

そこで必要になるのが、Threadクラスのオブジェクトを別に作ることです。

流れは次のようになります。

  1. Runnableを実装したクラスのオブジェクトを作る
  2. そのオブジェクトを引数にして Threadオブジェクトを作る
  3. Threadオブジェクトに対して start() を呼ぶ

表にするとこうです。

手順内容
1Runnable実装クラスのオブジェクトを作る
2そのオブジェクトを使って Threadオブジェクトを作る
3Threadオブジェクトを start() で起動する

ドラゴンボール風にいえば、戦士本人がそのまま出撃するのではなく、
まず「この戦士はこう動きます」という作戦内容を用意して、それを実際の出撃役に渡して動かすような感じです。

サンプルプログラム:Runnable実装を見てみよう

ここでは、Runnableインターフェイスを実装する形で、スレッドを作るシンプルな例を見てみます。

ファイル名:Sample6.java

class SaiyanTask implements Runnable
{
    private String name;

    public SaiyanTask(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)
    {
        SaiyanTask task1 = new SaiyanTask("ベジータ");

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

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

Sample6.javaの流れ

このコードでは、まず SaiyanTask クラスが Runnable を実装しています。
そして、その中に run() を定義して、別スレッドで行いたい処理を書いています。

ここが最初のポイントです。

class SaiyanTask implements Runnable

この宣言によって、SaiyanTask は Runnable を実装したクラスになります。
そして run() の中で、ベジータが修行の手順を確認しているメッセージを繰り返し表示します。

次に main() では、SaiyanTask のオブジェクトを作成しています。

SaiyanTask task1 = new SaiyanTask("ベジータ");

この時点では、まだ別スレッドは起動していません。
ここで作っているのは「別スレッドで何をするかを持ったオブジェクト」です。

そのあと、次のコードで Threadオブジェクトを作ります。

Thread th1 = new Thread(task1);

ここが Runnable を使う方法ならではの特徴です。
task1 を引数として Threadオブジェクトを作ることで、「この task1 の run() を別スレッドで動かす準備」ができます。

そして、最後に start() を呼び出します。

Thread th1 = new Thread(task1);

これで、新しいスレッドが起動し、task1 の run() が別の流れとして実行されます。

実行結果の見え方

実行すると、

  • 悟空が作戦の流れを整理しています。
  • ベジータが修行の手順を確認しています。

という2種類のメッセージが混ざって表示されます。

これは Thread継承で作ったときと同じで、main() の流れと別スレッドの流れが並行して進んでいるからです。
表示の順番は固定ではなく、実行環境によって変わることがあります。

ここで大切なのは、作り方は少し違っても、動きの本質は同じということです。
どちらの方法でも、start() をきっかけに新しいスレッドが動き、main() とは別の流れが生まれます。

Thread継承とRunnable実装の違いを整理する

ここで2つの方法を比べてみましょう。

項目Thread継承Runnable実装
クラス宣言extends Threadimplements Runnable
run() の定義必要必要
start() の呼び出し先自分自身のオブジェクトThreadオブジェクト
特徴形がわかりやすいほかのクラス継承と両立しやすい

この表から分かるように、Runnable実装の方法は、少し手順が増えます。
でもそのぶん、クラス設計の自由度が上がります。

なぜ Runnable の方法が柔軟なのか

Runnable の方法が便利なのは、スレッドとして動かしたい処理の内容と、実際にスレッドを動かす役を分けられるからです。

Thread継承の方法では、クラスそのものがスレッドとしてふるまいます。
一方、Runnable実装では、

  • Runnable実装クラス = 何をするか
  • Threadオブジェクト = どう動かすか

という形で役割が分かれています。

ドラゴンボール風にいえば、

  • 戦士が持つ修行メニュー
  • その修行メニューを実際に開始する仕組み

が分かれているようなものです。

この分離のおかげで、すでに別のクラスを継承している場合でも、Runnable を使ってスレッド処理を組み込みやすくなります。

図で見る Runnable方式の起動

この図が示していること

この図では、Runnableを実装した task1 をそのまま start() するのではなく、いったん Threadオブジェクトに渡してから起動していることを表しています。

ここから分かるのは、Runnable方式では

  • 処理内容を持つオブジェクトを作る
  • それを Thread に渡す
  • Thread を start() する

という3段階でスレッドが動き始めるということです。
この流れを理解すると、Runnable方式のコードがかなり読みやすくなります。

どちらの方法を覚えておくべきか

学習の最初の段階では、まず Thread継承の方法がわかりやすいです。
スレッドのしくみそのものを理解しやすいからです。

ただ、Javaではクラス設計の都合で Thread継承だけでは対応しにくいことがあります。
そのため、Runnable の方法もとても大切です。

最初は次のように覚えておくと整理しやすいです。

覚え方内容
基本をつかむThread継承で流れを理解する
柔軟に使うRunnable実装で設計の自由度を高める

ここで押さえておきたいポイント

今回の内容で特に大切なのは、次の点です。

ポイント内容
Javaは多重継承できない2つ以上のクラスを同時に継承できない
スレッド作成方法は2つあるThread継承とRunnable実装
Runnableでも run() を書く別スレッドで行う処理はやはり run() に書く
Runnable方式では Thread を作るRunnable実装オブジェクトを引数にして Threadオブジェクトを作る
start() は Thread に対して呼ぶRunnable実装オブジェクト自身には start() しない
動作の本質は同じどちらも別スレッドの流れを作るための方法

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

今回のテーマをドラゴンボールの世界で言い換えると、こうなります。

これまでの方法は、戦士そのものが最初から「スレッドとして戦う型」を受け継ぐ方法でした。
それに対して Runnable の方法は、戦士が「別行動で動くための任務内容」を身につけ、それを実際の出撃役に渡して戦わせる方法です。

どちらも最終的には別の処理の流れを生み出します。
違うのは、その流れをどの形で準備するかです。

つまり、スレッドを作る方法は1つではありません。
Javaでは、状況に応じて

  • Threadクラスを継承する
  • Runnableインターフェイスを実装する

という2つの方法を使い分けられます。

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