優しい夜
第9章 スレッド(SJC-P 1.4)
2007-05-14-Mon  CATEGORY: Java
≫目次
■スレッドの作成・実行
■現在実行されているスレッドの情報の出力
■スレッドの状態
■メソッド
■同期化とロック


スレッドの動作はJVMの種類によって大きく異なる。
同じJVMでも、スレッドの実行順序を決定するスケジューラの動作は毎回異なる。
どのJVMを使った場合でもきちんと動作するプログラムを設計すること。

デッドロックを起こさないこと。プログラムが動かなくなる。デッドロックが一旦起こると、解放されることは絶対にない。

※デッドロック…2つのスレッドがお互いに相手のロックを必要とし、その解放を待っている状態。どちらかのロックが解放されない限りどちらのスレッドも実行されないので、2つのスレッドは永久に待機したままとなる。

■スレッドの作成・実行
≪java.lang.Thread≫クラスを拡張する場合
1)Threadクラスを拡張
※オブジェクト指向の立場からすれば、Threadクラスを拡張してもよい場面とは、より具体的なバージョンのThreadクラスを作りたい時だけ。

2)run()メソッドをオーバーライド
class MyThread extends Thread{ //Threadクラスを拡張
    public void run(){         //オーバーライド
        System.out.println("Running in MyThread");
    }
}
3)スレッドのインスタンス化
4)スレッドの起動
MyThread t = new MyThread(); //インスタンス化。「初期状態」
t.start();                   //起動。「実行可能状態」
□Threadクラスの7つのコンストラクタ
いづれかを使用して、スレッドをインスタンス化する。
Thread() //Treadクラスを拡張した時に使用
Thread(Runnable target) //Runnableインタフェースを実装した時に使用
Thread(String name)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target, String name)
※Threadクラスは、Runnableインタフェースを実装しているので、Runnable型のインスタンスの代わりに、Thread型のインスタンスをコンストラクタの引数に与えることも出来る。

≪java.lang.Runnable≫インタフェースを実装する場合
1)Runnableインタフェースを実装
2)run()メソッドをオーバーライド
class MyRunnable implements Runnable{ //Runnableインタフェースを実装
    public void run(){                //オーバーライド
        System.out.println("Running in MyRunnable");
    }
}
3)スレッドのインスタンス化
4)スレッドの起動
MyRunnable r = new MyRunnable();
Thread t = new Thread(r); //インスタンス化。「初期状態」
Thread u = new Thread(r);
Thread v = new Thread(r); //同じジョブを行うスレッドをいくつも作成可能
t.start();                //起動。「実行可能状態」
u.start();
v.start();
なお、run()メソッドはRunnableインタフェースに宣言されている唯一のメソッドである。
public void run(){}

■現在実行されているスレッドの情報の出力
Thread.currentThread().getName()
スレッドの名前を取得するには、そのスレッドのインスタンスに対し、getName()メソッド(Threadクラスの非静的メソッド)を呼び出す。

Thread.currentThread()
Threadクラスの静的メソッド。現在実行中のスレッドの参照を返す。

setName()メソッド
Threadクラスの非静的メソッド。スレッドに明示的に引数で与えた名前を設定する。
例)t.setName("Fred");

■スレッドの状態
スレッドには以下の5つの状態がある。
「初期状態」
Threadインスタンスが作成されたが、まだstart()メソッドが呼び出されていない状態。
スレッドは、生存しているとみなされない。

「実行可能状態」
start()メソッドが呼び出された状態。まだ実行はされていない。
「実行中状態」や「待機/ブロック/スリープ状態」から、再び「実行可能状態」に戻ることもある。
スレッドは、生存しているとみなされる。

「実行中状態」
現在、まさにスレッドが実行されている状態。
「実行中状態」へ至る唯一の方法は、スレッドスケジューラによって「実行可能状態」から選ばれることのみ。
「実行中状態」が解除される要因はいろいろとある。
スレッドは、生存しているとみなされる。

「待機/ブロック/スリープ状態」
「実行可能状態」ではないものの、ある特定のイベントが発生すれば「実行可能状態」に戻る状態。
スレッドは、生存しているとみなされる。

「終了状態」
run()メソッドが完了すると、スレッドはこの状態になる。
いったん終了したスレッドは、二度と起動できない。
「終了状態」のThreadインスタンスに対して、start()メソッドを呼び出すとランタイムエラーIllegalThreadStateExceptionが発生する。
「終了状態」でも、start()メソッド以外は、呼び出し使用できる。

■メソッド
≪Threadクラス≫
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException
静的メソッド。チェック例外を投げる。ロックを持ち続ける。
指定ミリ秒(+ナノ秒)間スレッドを眠らせ(スリープ状態)、その後「実行可能状態」に戻す。
「実行可能状態」から、いつ「実行中状態」になるかは保証されない。保証されているのは、「最低限、指定ミリ秒(+ナノ秒)はスリープする」ということだけ。
従ってsleep()メソッドを使って、正確なタイマーを実現することはできない。

全てのコードは必ず何らかのスレッドによって実行されるので、sleep()メソッドはどこにでも記述できる。
通常は、アバウト時間(指定時間+α)ごとに動作させたいスレッドのrun()メソッド内に記述する。
記述例:
public void run(){
	for(int x=1; x<4; x++){
		System.out.println("HELLO");
		try{
			Thread.sleep(1000);
		}catch(InterruptedException e){}
	}
}
実行中のコードがsleep()メソッドに出会うと、現在実行中のスレッドがsleepする。
静的メソッドなので、必ず現在実行中のスレッドがsleepする。
静的メソッドなので、あるスレッドから別のスレッドをsleepさせることは出来ない。

静的メソッドをインスタンスを経由で呼び出す以下のような記述も可。
Thread.currentThread().sleep(1000);

public static void yield()
静的メソッド。ロックを持ち続ける。
現在、実行中のスレッド(自分自身)を「実行可能状態」に戻す要求を出すのみ。
※「実行中状態」から「実行可能状態」に戻る保証はない。yield()メソッドの動作は保証されていない。
(現在、実行中のスレッドと同じ優先順位のスレッドが「実行可能状態」にある場合、実行中のスレッド(自分)を「実行可能状態」に戻せる確率が高くなるらしいが、保証はなし。)

public final void setPriority(int newPriority)
非静的メソッド。
デフォルト(5)以外のスレッドの優先順位を直接設定できる。
記述例:
Thread t = new Thread();
t.setPriority(8); //低1〜10高。各JVMに必ず10段の優先順位があるとは限らない。
t.start();
『スレッドの優先順位』
スレッドには、そのスレッドを作成した実行スレッドと同じ優先順位が与えられる。
実行中のスレッドの優先順位が「実行可能状態」のスレッドの優先順よりも、低くなることはない、ということがほぼ保証されている。

『Threadクラスの3つの定数(final変数)』
Thread.MIN_PTIORITY //最低
Thread.NORM_PTIORITY //デフォルト
Thread.MAX_PTIORITY //最高

記述例:
Thread t = new Thread();
t.setPriority(Thread.MIN_PTIORITY); //最低の優先順位を設定
t.start();

public final int getPriority()
スレッドの優先順位を返す。

public final void join()
public final void join(long millis)
public final void join(long millis, int nanos)
非静的メソッド。ロックを持ち続ける。
実行中のスレッド(自分自身)を「待機状態」とし、指定のスレッドの後につなぐ。
指定のスレッドが「終了状態」になったら、自分自身を「実行可能状態」に戻す。
指定のスレッドが既に終了している場合は、自分自身を「実行中状態」のまま維持する。

記述例:
Thread t = new Thread();
t.start();
t.join(); //実行中のスレッド(自分自身)を指定のスレッド「t」の後につなぐ。

<join()メソッドのオーバーロードバージョン>
基本的に指定スレッドが終了するまで待つが、指定ミリ秒経過したら、「待機状態」をやめて「実行可能状態」に戻す。

public void run(){}
非静的メソッド。スレッドの行う処理を定義する。
Runnableインタフェースを実装するThreadクラスでは、実行文が空。

start()
非静的メソッド。スレッドの実行。「実行可能状態」にする。

public static boolean holdsLock(Object obj)
使用例:
Thread.holdsLock(this); //thisオブジェクトがロックされていればtrueを返す。

public void interrupt()
スレッドに割り込む。対象のスレッドがsleepやwaitで待機中の場合、チェック例外InterruptedExceptionをスローさせるメソッド。

≪Runnableインタフェース≫
public void run(){}
Runnableインタフェースの唯一のメソッド。引数を取らない。

≪Objectクラス≫
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout,int nanos) throws InterruptedException
非静的メソッド。チェック例外を投げる。唯一「ロック」を手放すメソッド。
指定のオブジェクトが「通知」するまで、処理を停止する。
同期コンテキスト内で呼び出す必要がある。
対象のオブジェクトのロックを取得しないと、そのオブジェクトに対して、wait()メソッドを呼び出すことは出来ない。
wait()メソッドを呼び出すスレッドが、ロックを取得していない場合には、IllgalMonitorStateException(ランタイムエラー)が投げられる。
待機中のスレッドはsleep()と同じ方法で割り込まれる可能性があり、その場合、InterruptedException(チェック例外)を投げることがある。
記述例:
synchronized(b){  //オブジェクトbのロックを取得しないと入れないし、
                  //オブジェクトbに対して、wait()メソッドも使えない。
	try{
        b.wait(); //オブジェクトbが「通知」するのをwaitする。ロックを一旦解放。
                  //「通知」が来ても、オブジェクトbのロックを再度取得できないと
                  //「実行可能状態」には戻れない。
	}catch(InterruptedException e){}
}
<wait()メソッドのオーバーロードバージョン>
(割り込みが発生しなければ)スレッドは「通知」を受け取るか、指定ミリ秒(+ナノ秒)が経過すると「待機状態」から抜ける。
ただし、ロックを一旦手放しているので、再度オブジェクトのロックを取得しないと「実行可能状態」には戻れない。

public final void notify()
非静的メソッド。ロックを持ち続ける。
同期コンテキスト内で呼び出す必要がある。
対象のオブジェクトのロックを取得しないと、そのオブジェクトに対して、notify()メソッドを呼び出すことは出来ない。
notify()メソッドを呼び出すスレッドが、ロックを取得していない場合には、IllgalMonitorStateException(ランタイムエラー)が投げられる。
どれか1つのスレッドに対してのみ「通知」する。(「通知」するだけで、その時点でロックは解放しない。)

記述例:
(this.)notify();

同じオブジェクト(上記の場合は、自分this)を複数のスレッドが待機している場合でも、その内のどれか1つのスレッドに対してのみ「通知」する。
待機しているスレッドがなければ、何も起こらない。

public final void notifyAll()
非静的メソッド。ロックを持ち続ける。
同期コンテキスト内で呼び出す必要がある。
対象のオブジェクトのロックを取得しないと、そのオブジェクトに対して、notifyAll()メソッドを呼び出すことは出来ない。
notifyAll()メソッドを呼び出すスレッドが、ロックを取得していない場合には、IllgalMonitorStateException(ランタイムエラー)が投げられる。
同じオブジェクトを待機している全てのスレッドに対して「通知」する。(「通知」するだけで、その時点でロックは解放しない。)

記述例:
(this.)notifyAll();


※wait()(待機メソッド)、notify()、notifyAll()(通知メソッド)の各メソッドは、同期コンテキスト内で呼び出す必要がある。
※スレッドは、対象のオブジェクトのロックを取得していない限り、そのオブジェクトに対して待機メソッドや通知メソッドを呼び出すことは出来ない。

■同期化とロック
データを保護するためには、
・変数をprivateにする。
・変数を変更するコードを同期化する。

Javaのオブジェクトは、スレッドに渡すためのロックを必ず1つ持っている。
メソッドを同期化するには、メソッドの前にsynchronizedキーワードを付ける。
メソッドを同期化した場合、スレッドはそのメソッドを呼び出す時に使ったオブジェクトのロックを取得する。

全てのオブジェクトは、そのオブジェクトからの「通知」を待機しているスレッドのリストを保持している。

同期化できるのはメソッドだけで、クラスや変数を同期化することはできない。
1つのクラス内に同期メソッドと非同期メソッドが混在して構わない。

1つのクラス内で2つ以上のメソッドを同期化した場合でも、その同期メソッドにアクセスできるのは1つのスレッドだけ。
つまり、あるスレッドがオブジェクトのロックを取得したら、他のスレッドはそのクラス(そのオブジェクト)の他のどの同期メソッドにも入ることができない。

1つのスレッドが、複数のオブジェクトのロックを取得することもできる。
1つのスレッドが、同じオブジェクトの他の同期メソッドのロックを取得することもできる。

静的メソッドは、そのクラスを表すjava.lang.Classクラスのインスタンスのロックを使って同期化することができる。

同期化はパフォーマンスを低下させるので、必要がなければ使用しない。

シングルスレッドでsynchronizedキーワードを使用してもエラーにはならない。

□同期ブロック
メソッド全体ではなくメソッドの中のコードブロックを同期化することもできる。
()内に、ロックを取得したいオブジェクト(Javaの世界では基本型を除く、あらゆるものがオブジェクト)のインスタンスを指定する。
オブジェクト以外(つまり基本型変数)は、スレッドに渡すロックを持たないので指定できない。
記述例:
synchronized(this){
	System.out.println("synchronized");
}
□スレッドがロックを取得できなかった場合
目的の同期メソッドのオブジェクトが既に他のスレッドに取得されていた場合は、ロックが解放されるまでスレッドは「待機状態」になる。
ロックが解放された後、そのロックを取得することが出来たら、「実行可能状態」になる。
同じロックを複数のスレッドが待機する場合もあるが、この時、一番長く待っていたスレッドがロックを取得するとは限らない。

Trackbacks0 Comments0
Comments

Only the blog author may view the comment.
 
Trackbacks
TB*URL
ページトップへ
Copyright © 2009 優しい夜. all rights reserved.