😸

マルチスレッドの基本<前編>[Java入門]

に公開

はじめに

こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。

記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。

対象読者

  • Javaを勉強中の方
  • Java SE11 Gold試験を勉強中の方
  • Javaのマルチスレッド、Threadクラス、Runnableインターフェースについて知りたい方

目次

1.javaのマルチスレッド
2.ThreadクラスとRunnableインターフェース

本文

1. javaのマルチスレッド

Java言語の基本は、シングルスレッドの逐次処理です。

Javaアプリケーションが起動すると、まずmainメソッドを実行するメインスレッドが一つ生成され、「逐次処理」が行われます。
「逐次処理」は、プログラムの命令が記述された順序通りに、一度に一つの処理ずつ実行される方式です。前の命令が完全に終了してからでないと、次の命令は開始されません。

シングルスレッドの逐次処理の利点は、構造がシンプルであるため処理の流れが理解しやすいというところです。
その反面、欠点もあります。I/O処理(ファイルの読み書き、ネットワーク通信など)のように時間がかかる処理を実行すると、CPUに余裕があるにも関わらず次の処理が実行できずに待たされてしまうのです。特に、大規模なシステムでは、この「待ち」の時間がパフォーマンスの低下につながります。

この「待ち」問題を解決するのが「マルチスレッド」です。

「マルチスレッド」は、複数の処理を別々のスレッドに割り当てることで、効率よく処理することができます。
java言語の「マルチスレッド」は、コアが1つのシングルコア環境では「並行処理」、コアが複数のマルチコア環境では「並列処理」となります。

「並行処理」は、ごく短い時間で処理を切り替える方式です。シングルコアでは、同時に複数のスレッドを実行することはできません。しかし、ごく短時間でスレッドを切り替えることで、ユーザーからは複数の処理が同時に進んでいるように見えるのです。「見かけ上の同時実行」ではありますが、CPUの待ち時間を減らし、システムの応答性を高めることができます。

「並列処理」は、複数の処理が実際に同時に実行される方式です。マルチコアでは、複数のコアが存在するため、それぞれのスレッドを別々のコアに割り当て、同時に処理を実行することが可能です。

2. ThreadクラスとRunnableインターフェース

シングルスレッドの状態から、別のスタックを作って、マルチスレッドで処理を行うには、java.lang.Threadクラスを利用します。

Threadクラスの実装方法は、主に2つあります。

  • Threadクラスを継承したサブクラスを定義する
  • java.lang.Runnableインターフェースを実装してクラスを用意し、そのインスタンスをThreadクラスのコンストラクタに渡す

1. Threadクラスを継承したサブクラスを定義する

1つ目の方法から説明していきます。

public class NewThread extends Thread {
  @Override
  public void run(){
    //新しいスレッドで実行したい処理
  }
}

1つ目の方法では、まず、上のコード例のようにThreadクラスのサブクラス(上のコードではNewThreadクラス)を実装します。次に、Threadクラスのrun()メソッドをオーバーライドし、新しいクラスで実行したい処理を記述します。

public class A {
  public static void main(String[] args) {
    //新しいスレッドを開始したい場所
    Thread t = new NewThread();
    t.start();
  }
}

Threadクラスのサブクラスが定義できたら、新しいスレッドを開始したい場所で、先ほどのサブクラス(NewThreadクラス)のインスタンスを生成し、start()メソッドを呼び出します。

この時、メソッド名を間違えないように注意が必要です。サブクラスを実装時にオーバーライドするのはrun()メソッドですが、新しいスレッドを開始したいときに呼び出すのはstart()メソッドです。新しいスレッドを開始したい時に、run()メソッドを呼び出しても、新しいスレッドは作成されません。

2. Runnableインターフェースを実装し、インスタンスをThreadクラスのコンストラクタに渡す

次に、2つ目の方法について見ていきましょう。

public class NewRunnable extends SuperClass implements Runnable {
  @Override
  public void run(){
    //新しいスレッドで実行したい処理
  }
}

まず、Runnableインターフェースを実装し、run()メソッドをオーバーライドし、新しいスレッドで実行したい処理を記述します。

public class A {
  public static void main(String[] args) {
    //新しいスレッドを開始したい場所
    Thread t = new Thread(new NewRunnable());
    t.start();
  }
}

次に、Runnableインターフェースを実装したクラスのインスタンスを、Threadクラスのコンストラクタに渡し、start()メソッドを呼び出します。

Runnableインターフェースを利用するメリットは、主に2つあります。

  1. Runnableインターフェースを実装するクラスに任意のスーパークラスを継承できる
    Javaではクラスの多重継承ができません。Threadクラスは「クラス」であるため、Threadクラスを継承したサブクラスは他のスーパークラスを継承することはできません。しかし、Runnableは「インターフェース」であるため、他のスーパークラスを継承することができます。

  2. コードが簡潔になる
    Runnableインターフェースはrun()メソッドのみを持つ、関数型インターフェースです。そのため、ラムダ式で簡潔に記述できます。
    以下は、上のコードをラムダ式で記述したものです。

//ラムダ式で記述
public class A {
  public static void main(String[] args) {
    //新しいスレッドを開始したい場所
    Thread t = new Thread(() -> {
      //新しいスレッドで実行したい処理
    });
    t.start();
  }
}

ラムダ式を利用することで、コードが簡潔になり、可読性が向上しています。

まとめ

  • Javaのマルチスレッドは、I/O処理などで発生しがちなCPUの「待ち時間」を有効活用し、パフォーマンスを向上させます

  • スレッドの実装方法にはThreadクラスを継承する方法とRunnableインターフェースを実装する方法の2種類があり、いずれもrun()メソッドをオーバーライドし、start()メソッドでスレッドを開始します

  • Runnableインターフェースを利用する方法は、Javaがクラスの多重継承を許さないという制約を受けず、またラムダ式で処理を簡潔に記述できるます


記事は以上です。

次回は、 Executorフレームワーク、Futureインタフェース、Callableインタフェースについてまとめる予定です。

最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion