Javaによるプログラミングに関する世界観3D(マルチスレッド)です。
C++では、C++11においてstd::threadが提供されるまで、標準のC++ではスレッドの概念がありませんでした。
このため、C++でマルチスレッドプログラミングを行うには、UNIX系のpthreadなど、プラットフォーム依存のコードを書かなければいけませんでした。
Javaでは、早くからスレッドの概念を導入し、標準のAPIとしてマルチスレッドアプリケーションを書くことができます。
詳しくは以下の書籍が参考になります。
C++(STL・ライブラリ)やLinux API(プロセス・メモリ)も参照のこと。
Javaのスレッドには、二通りのやり方がある。一つは、Threadクラスを継承する方法。もう一つは、Runnableインターフェースを実装する方法である。
Threadクラスの継承は単純なプログラム向けだが、複雑なプログラムになってくるとRunnableインターフェースを実装する方法を使うこともある。
後日注記:Runnableインターフェースを使う主な理由は、Javaでは多重継承ができないため。Javaでは単一のクラスしか継承することができないので、Threadクラスを継承すると別のクラスが継承できなくなる。このため、別のクラスを継承した上でマルチスレッドを行うためにRunnableインターフェースを実装する。どちらの場合でもrun()メソッドをオーバーライドして、newでインスタンスを生成してstart()メソッドをmain()などから実行すればよい。
以下はThreadクラスを継承する方法。
public class CountByThread extends Thread { public static void main(String[] args) { CountByThread ct = new CountByThread(); ct.start(); // main()で行う処理をここに書く } @Override public void run() { // run()で行う処理をここに書く } }
以下はRunnableインターフェースを実装する方法。
public class CountByRunnable implements Runnable { public static void main(String[] args) { CountByRunnable cr = new CountByRunnable(); Thread th = new Thread(cr); th.start(); // main()で行う処理をここに書く } @Override public void run() { // run()で行う処理をここに書く } }
run()メソッドで行う処理は、main()スレッドで行う処理とは並行して実行される。
ほかのクラスでもThreadクラスを継承あるいはRunnableインターフェースを実装してrun()メソッドを書けば、main()メソッドなどからさらに多くのスレッドを実行できる。
(以上はJava言語プログラミングレッスン 第3版(下) オブジェクト指向を始めようを参考に執筆しました。)
インスタンスのメソッドを実行する場合、メンバ変数(フィールド)の値を変更するようなメソッドを書くことがある。
これは単一スレッドで実行する場合は問題がないが、同一インスタンスのメンバ変数を編集する場合、そのメソッドをマルチスレッドで並列して複数実行させると、データの変更中に別のスレッドからデータを変更してしまい、データの矛盾が起きてしまうことがある。
このような時には、そのメソッドにsynchronizedというキーワードをつける。synchronizedメソッドは、シングルスレッドでしか実行されないように、実行中にインスタンスがロックされる。このため、このような場合にはデータを変更するメソッドをsynchronizedメソッドにすればいい。ブロックに対して同じことをしたい場合はsynchronizedブロックを使うことができる。
スレッドの一時停止は、Thread.sleep()で行える。
もし、別のスレッドの処理が終わるまで待機し、別のスレッドの処理が終わった段階で実行を再開したいなら、join()メソッドを使うことができる。
また、スレッド同士の間で待ち合わせを行う場合、wait()メソッドを実行すれば、通知が行われるまでそのスレッドは待機する。notify()メソッドやnotifyAll()メソッドを実行すると、待機していたwait()に対して通知を行い、ロックが解除された時点でwait()が再開する。
このように、マルチスレッドにしても問題がないようなプログラムのことをスレッドセーフと呼ぶ。
(以上はJava言語プログラミングレッスン 第3版(下) オブジェクト指向を始めようを参考に執筆しました。)
スレッドセーフとは、マルチスレッドの処理にしても問題が無い並列処理のプログラムのこと。
原則 | 説明 |
---|---|
ローカル変数を使う | 複数のスレッドから変更されるかもしれないような、 クラス変数やインスタンス変数を使わず、 できるだけローカル変数を使う。 ローカル変数は複数のスレッドから共有されないため。 |
アトミック(単一不可分) | メソッドにsynchronizedを付加してロックを行う。 競り合い状態を発生させない。 |
synchronizedメソッドやsynchronizedブロックの中の処理は、単一のスレッドでしか実行できない。
詳しくは以下のページを参考のこと(上の文章は以下のサイトを参考にしました)。
スレッド関係のAPIとしては、以下のようなものがある。
API | 説明 |
---|---|
Thread.sleep() | 一定時間、スレッドを停止状態にする |
Thread.join() | 指定したスレッドの処理が終わるまで待ち、 そのスレッドの処理が終わってから処理を継続する |
Thread.interrupt() | なんらかの処理をしているスレッドを中断させる (割り込みを行う) |
ポーリング(定期的な問い合わせ)、コールバック(終わった時に呼び出す)、データ共有用オブジェクト(オブジェクトを共有)を使うことで、処理結果を受け取ることができる。
詳しくは以下のページを参考のこと(上の文章は以下のサイトを参考にしました)。
Javaのマルチスレッドについて、以下の書籍がマルチスレッドのデザインパターンを説明されています。Javaでマルチスレッドのプログラムの設計と実装をする皆さんにおすすめします。
以下の書籍が参考になります。
並列処理も参照のこと。