スレッドとは?
スレッドとは、プログラム内で同時に実行されるタスクのことです。
複数のスレッドを組み合わせることで、
プログラムの効率やレスポンス速度を向上させることが可能です。
このように、複数のスレッドを扱うプログラミング手法を、
「マルチスレッドプログラミング」と言います。
マルチスレッドプログラミングの利点は、時間のかかる処理がある場合に、
ユーザーにただ待たせるのではなく、待っている間に別の画面(一部分)を
見せたりすることができる点です。
どうしても処理が長くなってしまう場合は、マルチスレッドの導入を検討してみても良いと思います。
ただし、注意が必要な部分もあります。
複数のスレッドが同じ共有リソースへのアクセス競合や、デッドロックのリスクがあります。
そういった点に考慮できる適切な同期メカニズムの選択や、
スレッドセーフ(安全)なプログラムを実装する必要があります。
スレッドは、非常に便利な機能ですが、特性を知らないと思わぬバグが発生する恐れもあります。
また、テストしづらいので、より一層の注意が必要になります。
Column:デッドロックとは?
デッドロックとは、複数のスレッドがお互いに相手の持つリソースを待ち続けてしまい、
進行が停滞してしまう現象のことです。
スレッドの基本
スレッドを作成するためには、Threadクラスか、Runnableインターフェースを使用します。
この2つの使い分けの選択基準となるのは、
「そのスレッドを再利用したいか」が一般的な選択基準となります。
特定のスレッドを独自にカスタマイズしたい場合は、Threadクラスを使用します。
汎用的に使用したい場合は、Runnableインターフェースを使用します。
スレッドの作成
・Threadクラスを使用する場合
Threadクラスを継承した、新しいクラスを作成します。
そして、その中でrunメソッドをオーバーライドします。
// extendsキーワードを使ってThreadを継承
class MyThread extends Thread {
public void run() {
// 実行内容を記述
}
}
・Runnableインターフェイスを使用する場合
Runnableインターフェースを実装した新しいクラスを作成します。
そして、その中でrunメソッドをオーバーライドします。
// implementsキーワードでRunnableインターフェースを実装
class MyRunnable implements Runnable {
public void run() {
// 実行内容を記述
}
}
スレッドの開始
・Threadクラスを使用する場合
// Thread型としてインスタンス生成
Thread myThread = new MyThread();
// スレッドの開始
myThread.start();
・Runnableインターフェースを使用する場合
// Runnableインターフェース型としてインスタンス生成
// MyRunnableはクラスのため、インスタンス生成が可能
Runnable myRunnable = new MyRunnable();
// Thread型としてThreadのコンストラクタへmyRunnableを渡す
Thread myThread = new Thread(myRunnable);
// スレッドの開始
myThread.start();
スレッドの実行
スレッドを開始すると、runメソッド内の処理が実行されます。
ここで注意しないといけないことは、共有リソースへのアクセスです。
アクセス競合やデッドロックが発生しないように気をつけましょう。
※回避方法は、後述します。
スレッドの終了
スレッドは、runメソッド内の処理が終われば、自動的に終了します。
明示的に終了させたい場合は、「return;」を記述することで終了させることができます。
ここでは、stopメソッドを使用することもできますが、
不整合が発生する可能性があるようで非推奨となっています。
スレッドの同期
スレッドの同期とは、
複数のスレッドが共有リソースへアクセスする際の競合を防ぐための仕組みです。
同期を行うことで、スレッド間の相互作用や順序の制御を行い、安全性を保つことが可能です。
以下は、同期に関する要素の詳細です。
相互排除
相互排除とは、複数のスレッドが共有リソースにアクセスすることを制御することです。
この機能は、synchronizedキーワードやLockインターフェースを使用して実現します。
モニター
モニターとは、相互排除とスレッド同士の通信を組み合わせた同期のことです。
この機能を使用する場合は、synchronizedキーワードを使用します。
synchronizedは変数やメソッド、クラスに対して付与できます。
その場合、対象が呼び出される際は1つのスレッドしか実行できません。
ロック
ロックとは、スレッドが共有リソースへアクセスするためのものです。
Lockインターフェイスとその実装クラスのReentrantLockクラスを使用して表現します。
プラグラマーによって取得や解放をする必要があります。
ロックは、スレッドの再開や待機などの機能を持っています。
条件変数
条件変数とは、特定の条件を満たすまで待機させるためのものです。
一般的には、上記の「ロック」と組み合わせて使用します。
スレッドの通信
スレッドには、複数のスレッド間で互いにアクセスし、リソースを共有する仕組みがあります。

複数のスレッドを使用していて、
ある特定の処理が終わるまでは、この処理の実行を待ちたい
このような状況の際に、スレッドはとても役に立ちます。
スレッド間の通信を実現するためのメソッドは以下の3つです。
また、下記で紹介しているwait()とnotify()は、
必ずsynchronizedブロック内で使用する必要があります。
そうすることで、モニターを正しく管理することができ、スレッドの競合を防げます。
wait()
wait()は、スレッドを待機状態にします。
下記のnotifyメソッドやnotifyAllメソッドでの通知が来るまで、待機状態が継続されます。
notify()
notify()は、待機状態のスレッドを1つだけランダムに選択し、通知を送ります。
この処理によって通知されたスレッドは、待機状態から解放されます。
notifyAll()
notifyAll()は、待機状態のスレッド全てに通知を送ります。
この処理によって通知されたスレッドは、待機状態から解放されます。
まとめ
今回は、マルチスレッドプログラミングについて解説しました。
マルチスレッドプログラミングは、高機能なシステムを開発する上では、欠かせない機能です。
そのため、安全に使いこなすことで、処理の効率化に繋がります。
ただし、安全な開発を行うには考慮すべき点が多く、学習コストが高いのがデメリットです。
コメント