Java アドバンス

Java スレッド (Threads)

1. Java スレッド(Threads)とは

スレッド(Threads)を利用することで、プログラム内で複数の処理を同時に実行し、より効率的に動作させることが可能になります。

スレッドを使用すると、メインプログラムの動作を妨げることなく、複雑なタスクをバックグラウンドで実行できるようになります。

2. スレッドの作成

スレッドを作成するには、主に2つの方法があります。

2.1 Thread クラスを継承する方法

一つ目は、Thread クラスを継承(Extend)し、その run() メソッドをオーバーライドする方法です。

// Threadクラスを継承したクラス定義
public class Main extends Thread {
  public void run() {
    System.out.println("このコードはスレッド内で実行されています");
  }
}

2.2 Runnable インターフェースを実装する方法

もう一つの方法は、Runnable インターフェース実装(Implement)することです。

// Runnableインターフェースを実装したクラス定義
public class Main implements Runnable {
  public void run() {
    System.out.println("このコードはスレッド内で実行されています");
  }
}

3. スレッドの実行

作成したスレッドを実際に動かす方法は、どちらの方法で作成したかによって異なります。

3.1 Thread クラスを継承した場合の実行

クラスが Thread を継承している場合、クラスのインスタンスを作成し、start() メソッドを呼び出すことでスレッドが開始されます。

public class Main extends Thread {
  public static void main(String[] args) {
    Main thread = new Main();
    thread.start(); // スレッドを開始
    System.out.println("このコードはスレッドの外側(メイン)で実行されています");
  }
  public void run() {
    System.out.println("このコードはスレッド内で実行されています");
  }
}

3.2 Runnable インターフェースを実装した場合の実行

クラスが Runnable を実装している場合、そのクラスのインスタンスを Thread オブジェクトのコンストラクタに渡し、その後に Thread オブジェクトの start() メソッドを呼び出します。

public class Main implements Runnable {
  public static void main(String[] args) {
    Main obj = new Main();
    Thread thread = new Thread(obj); // ThreadオブジェクトにRunnableインスタンスを渡す
    thread.start(); // スレッドを開始
    System.out.println("このコードはスレッドの外側(メイン)で実行されています");
  }
  public void run() {
    System.out.println("このコードはスレッド内で実行されています");
  }
}

4. 「継承」と「実装」の違い

最大の相違点は、Javaの単一継承の制約にあります。Thread クラスを継承してしまうと、他のクラスを継承することができなくなります。一方、Runnable インターフェースを実装する方法であれば、class MyClass extends OtherClass implements Runnable のように、他のクラスを継承しつつスレッドとしての機能を持たせることが可能です。

5. 並行性問題(Concurrency Problems)

スレッドはプログラムの他の部分と同時に実行されるため、コードがどの順番で実行されるかを事前に知る術はありません。スレッドとメインプログラムが同じ変数に対して読み書きを行うと、その値は予測不能になります。これによって生じるバグを並行性問題(Concurrency Problems)と呼びます。

5.1 並行性問題の具体例

変数 amount の値が実行のタイミングによって予測不能になるコード例です。

public class Main extends Thread {
  public static int amount = 0;

  public static void main(String[] args) {
    Main thread = new Main();
    thread.start();
    System.out.println(amount); // ここでの値は予測不能
    amount++;
    System.out.println(amount); // ここでの値も予測不能
  }

  public void run() {
    amount++; // スレッド側で加算
  }
}

5.2 解決策:isAlive() の活用

並行性問題を避けるためには、スレッド間で共有する属性(変数)を極力減らすことがベストプラクティスです。属性を共有する必要がある場合の一つの解決策として、isAlive() メソッドを使用してスレッドの終了を確認してから、共有変数を利用する方法があります。

public class Main extends Thread {
  public static int amount = 0;

  public static void main(String[] args) {
    Main thread = new Main();
    thread.start();
    
    // スレッドが終了するまで待機
    while(thread.isAlive()) {
      System.out.println("待機中...");
    }
    
    // スレッド終了後にamountを更新し、出力する
    System.out.println("Main: " + amount);
    amount++;
    System.out.println("Main: " + amount);
  }

  public void run() {
    amount++;
  }
}