在籍している大学(UoPeople)の課題で、Javaでマルチスレッドを利用したプログラムを書くものがあった。自分はマルチスレッドが必要になるアプリケーション開発を実務で経験したことなく、学ぶことが多かったので、記事にまとめる。
マルチスレッドとは
そもそもスレッドに関して、wikipediaでの定義は以下
スレッド(thread)とは、コンピュータプログラムにおいて特定の処理を行うための一貫性のある命令の流れのことであり、プロセッサ利用の最小単位。プロセスは少なくとも1つ以上のスレッドを含む。一般的に各プロセスには独立した仮想アドレス空間が割り当てられるが、プロセス内のスレッド群はアドレス空間を共有する。そのためプログラムを実行するときのコンテキスト情報が最小で済み、同じプロセス内でスレッドを切り替える際はアドレス空間の切り替えが不要となるので、切り替えが高速になる。
複数スレッドを生成し、分けて処理を行うことで、高パフォーマンスを実現することができる。プロセス内で複数スレッドを作成して、処理を分散することを並行処理を呼ぶ。一方で、複数のプロセスを作成して、処理を分散することを並列処理と呼ぶ
プロセスとスレッド
前提として押さえておきたいのは、プロセスとスレッドの違いである。wikipediaでのプロセスの定義は以下。
プロセスとは、処理のことである。情報処理においてプログラムの動作中のインスタンスを意味し、プログラムのコードおよび全ての変数やその他の状態を含む。オペレーティングシステム (OS) によっては、プロセスが複数のスレッドで構成される場合があり、命令を同時並行して実行する。
スレッドとは、プロセス内の実行の単位であり、プロセスの中でスレッドが展開される。また、プロセスは独立したメモリアドレス空間を保持している。
ポイントは、複数スレッドを展開する場合、そのスレッドが展開されているプロセスのメモリアドレス空間が共有されている、という点である。つまり、スレッドから共有されているメモリアドレス空間をにアクセスすることができる。
共通のメモリにアクセスをして、データの整合性が保てずに、何かしらのバグが生じることをレースコンディションと呼ぶ。それを避けるために、各スレッド間では、共通のメモリアドレス空間を意識した実装 = スレッドセーフな実装をする必要がある。
マルチスレッドの作り方
Thread クラスを継承する
Threadクラスを継承し、runメソッドをオーバーライドしてクラスを作成する。
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
Runnable インターフェースを実装する
Runnableインターフェースを実装したクラスを作成する。
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Runnable: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Runnable interrupted.");
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
スレッドセーフ
スレッドは、プロセスと異なり、同じメモリアドレス空間を共有する。そのため、同一プロセス内の複数のスレッドが同じメモリ領域にアクセスする場合、データの整合性を担保するための考慮が必要である。ここでは、synchronizedとvolatileについて触れる。
synchronized
synchronized キーワードは、そのインスタンスにおいて、synchronizedキーワードが記述されているメソッド、もしくはブロックの実行を、一つのスレッドに制限するもの。
class SynchronizededSample {
synchronized void someMethod() {
doSomething();
}
}
volatile
volatile キーワードは、変数の値が予期せぬ方法でキャッシュされるのを防ぎ、すべてのスレッドが常にメインメモリから最新の値を読み取ることを保証する。一方で排他制御を保証しているわけではない点は注意。
class SynchronizededSample {
private static volatile boolean running = true;
...
}
まとめ
- スレッドとは、プロセス内で展開できるプロセッサ利用の最小単位であり、複数のスレッドが同時に実行されることでマルチタスキングと並行処理が可能になる。
- Javaでは、Threadクラスを継承した、もしくはRunnableインターフェースを実装したクラスを実装することで、マルチスレッドに対応した処理を実現することができる
- 複数スレッドを作成する場合、メモリアドレス空間が共有される
- レースコンディションを避けるために、synchronizedやvolatileを利用して、スレッドセーフな実装を考慮する必要がある