12章 マルチスレッド | |
1.スレッドとは | |
一つのプロセスで、同時に複数の処理をすることができる。例えば、ブラウザでインターネットからファイルを取得する仕事が動き、さらにユーザからの操作を受け付ける仕事があり・・・これらが同時に動いている。この仕事一つ一つをスレッドという。 複数スレッドが同時に処理することができるプログラムをマルチスレッドプログラムという。 |
|
2.スレッドのライフサイクル | |
同時にスレッドが動くといっても実際はCPUが一つなので、一つ目のスレッドが動いたら二つ目のスレッドが動く、というように短時間の間に交互に動く。これをタイムディビジョン方式という。 | |
![]() 現在、実行されているスレッド 待機状態 通信の返事が返ってくるまで待つなどキーボード入力を待ったり、イベントが終了するまで待機している状態。または、何もすることがない状態。 実行可能状態 本来は、すぐに実行になりたいが別のスレッドが実行中の場合は、一時待つ。 |
|
3.スレッドの作成 〜Threadクラス〜 | |
スレッドを作るには、java.langパッケージのThredクラスを継承すれば作られる。さらに、スレッドの中身はrunメソッドをオーバーライドして記述する。そして、スレッドクラスのstartメソッドを呼び起こせば、新しいスレッドが作成されrunメソッドが別スレッドとして実行される。 | |
Test.java// 独自のスレッドクラス class MyThread extends Thread{ public MyThread( ){ } // このrunメソッドがスレッド本体 public void run( ){ System.out.println(“MyThread Start!”); // 10回500ミリ秒毎n”Hello”と表示する public class Test { // スレッドを開始 // 1秒毎に”Hi !”と10回表示する |
|
C:\java>javac Test.java
C:\java>java Test |
|
4.Threadクラスのメソッド | |
●コンストラクタ概要 | |
Thread( ) 新しいThreadオブジェクトを割り当てる |
|
Thread(Runnable target ) 新しいThreadオブジェクトを割り当てる |
|
Thread(Runnable target,String name) nameという名前の新しいThreadオブジェクトを割り当てる |
|
Thread(String name) nameという名前の新しいThreadオブジェクトを割り当てる |
|
●メソッド概要 | |
String getName( ) このスレッドの名前を返す |
|
boolean isAlive( ) このスレッドが生存しているかどうかを判定する |
|
void join(long millis) このスレッドが終了するのを、最高でmillisミリ秒待機する |
|
void run( ) このスレッドが別個のRunnable実行オブジェクトを使用して作成された場合、そのRunnableオブジェクトのrunメソッドが呼び出される |
|
void setName(String name) このスレッドを、デーモンスレッドまたはユーザスレッドとしてマークする |
|
void setPriority(int newPriority) このスレッドの優先順位を変更する |
|
static void sleep(long millis) 現在実行中のスレッドを指定されたミリ秒数の間、スリープ(一時的に実行を停止)させる |
|
void start( ) このスレッドの実行を開始 |
|
static void yield( ) 現在実行中のスレッドオブジェクトを一時的に休止させ、ほかのスレッドが実行できるようにする |
|
5.スレッドの作成 〜Runnableインターフェース〜 | |
Test.java// 独自のクラス class MyRunnable implements Runnable{ // このrunメソッドがスレッド本体 public void run( ){ System.out.println(“MyRunnable Start!”); // 10回500ミリ秒毎n”Hello”と表示する public class Test { // スレッドを開始 // 1秒毎に”Hi !”と10回表示する |
|
C:\java>javac Test.java
C:\java>java Test |
|
6.同期 | |
ロックとは、一つ目のスレッドがあるメソッドをロックすると別のスレッドはそのメソッド内に入れなくなる。そのメソッドを出るときにロックを解除することで別スレッドがそのメソッドに入ることができるようにする。 メソッドを作成するときに「Synchronized」修飾子をつける。javaではこれだけでロックをかける(同期をとる)ことができる。 |
|
Test.java// 口座クラス class Account{ private int balance = 0;
synchronized void deposit ( int amount ){ // 顧客クラス // コンストラクタ // 10円ずつ貯金することを1000回繰り返す class Test{ // 10人の顧客を作る // 10人の顧客が一つの口座に振り込み処理を } // 10人のスレッドが終わるのを待つ // 残高表示 |
Test.java// 口座クラス class Account{ private int balance = 0; void deposit ( int amount ){ int getBalance( ){ // 顧客クラス // コンストラクタ // 10円ずつ貯金することを1000回繰り返す class Test{ // 10人の顧客を作る // 10人の顧客が一つの口座に振り込み処理を } // 10人のスレッドが終わるのを待つ // 残高表示 |
C:\java>javac Test.java
C:\java>java Test |
C:\java>javac Test.java
C:\java>java Test |
メソッドにsynchronized修飾子をつけて同期をとる方法ではなく、オブジェクトにロックをかけて同期をとる方法 | |
Test.java// 口座クラス class Account{ private int balance = 0; void deposit ( int amount ){ int getBalance( ){ // 顧客クラス // コンストラクタ // 10円ずつ貯金することを1000回繰り返す class Test{ // 10人の顧客を作る // 10人の顧客が一つの口座に振り込み処理を開始する } // 10人のスレッドが終わるのを待つ // 残高表示 |
|
C:\java>javac Test.javaC:\java>java Test 残高:100000 C:\java> |
|
この方法は、accountにロックをかけている。一つ目のスレッドがaccountにロックをかけると、別のスレッドはロックが解除されるまで、synchronizedで囲まれたブロック(synchronizedブロック)には、入ることができない。 | |
7.デットロック | |
デットロックとは、マルチスレッドプログラミングで起こりやすいバグの一つ。一つ目のスレッドがXというオブジェクトのロックが解除されるのを待つ。二つ目のスレッドはYというオブジェクトをロックしており、Xというオブジェクトの解放を待つ。このような場合、両方のスレッドはオブジェクトの解放を永遠に待ち続けてしまうため、プログラムは進まなくなり、いわゆる暴走の状態に陥る。このように、複数のスレッドがお互いにロックの解放を永久に待ち続けてしまうことをデットロックという。 | |
Test.java class X{ int x; } class Y{ class A extends Thread{ A(X x, Y y){ void func1( ){ void func2( ) { public void run( ){ class Test{ A a[ ] = new A[10]; |
|
C:\java>javac Test.javaC:\java>java Test func1:start func1:start func1:start func1:start func1:start func1:start func1:start func1:start func2:start func1:start func1:start func2:start ←ここでデットロックが発生
|
|
多くの場合、ロックするオブジェクトを同じ順番でロックすれば、デットロックは防げる。例えば上記では、func2のsychronizedでロックする順番をfunc1と同じように「x→y」の順番でロックすればデットロックは防げる。言い換えるとオブジェクトに優先順位を与えるということになる。 このようにマルチスレッド特有のバグが発生した場合の多くは、バグを再現させることすら難しく、デバッグ作業は大変な作業になってしまう。バグが起きないようにあらかじめちゃんと設計してからプログラミングする必要がある。 |
|
8.スレッド間の通信 | |
スレッドから一時的にロックを解放し、他のスレッドにsynchronizedメソッドもしくはsynchronizedブロックを実行するチャンスを与えることができる。例えば、一つ目のスレッドはメッセージを受信するスレッドで、二つ目のスレッドはメッセージを与えるスレッドである。メッセージ受信のスレッドは、メッセージボックスにメッセージがなければメッセージが格納されるまですることがない。このような場合ほかのスレッド(メッセージ送信スレッド)に制御を移す。逆にメッセージ送信スレッドは、メッセージボックスがいっぱいならば、メッセージ受信スレッドがメッセージを処理し終わるまで待たなければならない。 このように、他のスレッドが処理し終わるまで待つ場合は「wait」メソッドを使用し、他のスレッドに制御を与える場合は「notify」または「notifyAll」メソッドを使用する。notifyAllメソッドはwaitしているすべてのメソッドに対して通知をするが、notifyメソッドは、いずれか一つのスレッドに対してのみ通知する。どのスレッドに通知されるかは、JVMが勝手に決めていいことになっている。 |
|
Test.java import java.util.*;
// メッセージボックス // メッセージボックスにメッセージを入れる // メッセージを入れる // メッセージを入れ終わったら別のスレッドを起こす // 以下例外処理 // メッセージ取り出し // メッセージ取り出し // 取り出したら別のスレッドを起こす // 以下例外処理 // メッセージを取り出し、表示するスレッド class Test{ public static void main(String args[ ]){ MessageSender sender = new MessageSender( ); try{ |
|
C:\java>javac Test.javaC:\java>java Test 0.2894081823761079 0.20714542091971344 中略 0.519766868805261 C:\java> |
|