はじめに
こんにちは。株式会社KANGEN Holdingsに所属しているhakushakuです。
今回はJavaによるスレッド処理を解説していきます。
そもそもスレッド処理ってなんだ?
ここで紹介する処理は逐次処理も含めて3種類あります。
まずは下記をご覧ください。
処理方式 | 説明 | 特徴 |
---|---|---|
逐次処理 | 処理を1つずつ順番に実行する方式。 | 一度に1つのタスクを実行。全ての処理が完了してから次の処理が始まる。 |
並列処理 | 複数のタスクを同時に実行する方式。複数のプロセッサやコアを使用。 | 同時に複数の処理を実行。処理の独立性が必要。 |
並行処理 | 複数のタスクが「同時に進行」するように見えるが、実際には時間的に交互に実行される。 | タスクは並列に見えるが、実際には1つのプロセッサで順番に処理される。 |
逐次処理のイメージとポイント
・タスクを1つずつ順番に実行します。スレッドによる処理を施さなければこれで実行されます。
・同時には実行しません。1つの処理が終わってから次の処理が始まります。
・実装が簡単で、タスク間の依存関係がある場合に適している。
・タスクが多い際に、処理に時間を要することがあります。
並列処理のイメージとポイント
・複数のタスクを同時に実行します。
・複数の処理を並行して実行します。複数のプロセッサやコアを利用します。
・処理速度が速くなります。タスクが同時に実行されるため処理効率が良くなります。
・処理が独立しておりインスタンスを共有する場合など、タスク間でのデータのやり取りに注意が必要。
並行処理のイメージとポイント
・タスクが同時実行されているように見えますが、高速で交互に処理されています。
・1つのプロセッサがタスクを順番に処理するため、タスクが重なって見えます。
・複数のタスクを効率的に処理できるので、リソースを有効活用できます。
・タスク間の切り替えを要するので、オーバーヘッドが発生する可能性をはらんでいる。
スレッド処理とは複数の処理をプログラム上で同時に動かす仕組みとなります。よって並列処理、並行処理がスレッド処理となります。
上記で解説していますが、並列処理と並行処理は厳密には異なる動きとなりますので注意が必要です。
どうやってスレッド処理を実装するの?
スレッド処理を実装するには、2種類の方法があります。
1つ目はThreadクラスのサブクラス、つまりThreadクラスを継承したクラスを作成する方法です。
2つ目はRunnableインターフェースを実装したクラスを作成する方法です。
兎にも角にもコーディングしてみましょう。
まずは1つ目のThreadクラスを継承する方です。
public class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("スレッド処理が実行されました");
}
}
スレッド処理を実装するためにThreadクラスを継承し、Runメソッドをオーバーライドします。
ここではメインスレッドと非同期でスレッド処理が動いていることを確認するため、5秒間sleepしたあとに文字列を出力します。
public class Main {
public static void main(String[] args) {
System.out.println("メインクラス開始");
MyThread myThread = new MyThread();
myThread.start();
System.out.println("メインクラス終了");
}
}
呼び出す側のクラスでは作成したサブクラスをインスタンス生成しstartメソッドを呼び出します。
runメソッドを呼び出すわけではないことに注意が必要です。
実行結果を確認します。
メインクラス開始
メインクラス終了
スレッド処理が実行されました。
スレッド処理が実装できていることを確認できます。
これが逐次処理であった場合、メインクラス開始→スレッド処理が実行されました。→メインクラス終了と出力されます。
次にRunnableインターフェースを実装する方です。
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("スレッド(Runnable)処理が実行されました。");
}
}
実装に関しては1つ目と大差ありません。
public class Main {
public static void main(String[] args) {
System.out.println("メインクラス開始");
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
System.out.println("メインクラス終了");
}
}
呼び出し元では1つ目とすこし違いがあります。
まずRunnableインターフェースを実装したクラスをインスタンス生成し、そのインスタンスをThreadクラスに渡しstartメソッドをよび出すことで実行します。
実行結果を確認します。
メインクラス開始
メインクラス終了
スレッド(Runnable)処理が実行されました。
スレッド処理がちゃんと動作していることを確認できました。
さいごに
スレッド処理は強力な仕組みですが、実装方法によってはデッドロックが発生したり、共有インスタンスで期待通りに動かない可能性もあります。次回はスレッドセーフやデッドロックになってしまう原因などを解説したいと思います。
参考文献
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Thread.html#run--