スレッドとは制御の流れのこと。
マルチスレッドでは複数の制御の流れを作成できる。
POINT
スレッドは "ながら仕事" をする時に使う。
POINT
スレッドは次のケースで使う。
■ スレッドをつくる
スレッドをつくる方法は2つある。
// 仕事をつくる
Runnable job = new MyJob();
// Thread には Job が必要
Thread t = new Thread( job );
// 実行可能 状態になる
t.start();
POINT
Thread には名前をつけることができる。
デバッグをする時に便利。
Thread t = new Thread();
// 名前をつけなくてもデフォルトの名前がつく
t.setName("xxx");
// 実行中のスレッドの名前をとる
Thread.currentThread().getName();
Runnable を実装したクラスをスレッドで実行する。
// スレッドで実行可能なクラス
class Job implements Runnable
{
public void run() {
while(true) {
System.out.println("thread working" );
}
}
};
public class TestRunable {
public static void main (String[] args) {
Job job = new Job();
Thread t1 = new Thread( job, "test" );
t1.start();
System.out.println("main thread run");
}
}
WARNING
スレッドの run() は一度しか実行できない。
スレッドへの仕事を繰り返す場合は 無限ループさせる。
t = new Thread();
t.start();
// ERROR
// 一度したスレッドは再度実行できない。
t.start();
public void run() {
while ( true ) {
System.out.println("thread working" );
}
}
■ スレッドクラスを継承する
public class Test extends Thread {
public main() {
}
}
■ スレッドを止める
POINT
スレッドの動作をとめるには次の方法がある。
スレッドの終了をまつ。
WARNING
一度終了したスレッドの再利用( start() )はできない。
明示的に破棄する場合は null を設定して参照を消すこと。
class Test extends Thread {
public static void main ( String args[] ) {
Test t = new Test();
t.start();
// スレッドが生きているか質問する。
while ( t.isAlive() ) {
}
}
public void run() {
System.out.println( "thread working ..." );
}
}
■ スレッドの終了をまつ
main スレッドで isAlive() で質問をしながら待つと CPU 時間を無駄に消費することになる。
そのため join() メソッドで指定したスレッドが停止するまでまつ
// スレッドを実行して
t.start();
// スレッドの終了をまつ
t.join();
// 2 秒まつ
t.join( 2000 );
■ 別のスレッドからとめる
スレッドは run() メソッドが完了すれば, 終了するので
run() メソッドが完了するようにすればいい。
つまり終了条件となる変数の値を変更してしまえばいい。
class TestStop extends Thread {
// 終了条件
static volatile boolean flag = false;
public static void main ( String args[] ) {
TestStop t = new TestStop();
int i = 0;
t.start();
System.out.println("スレッドを止めるには s と入力してください");
try {
InputStreamReader fp = new InputStreamReader(System.in);
while(true) {
i = fp.read();
if (i == '\n') break;
System.out.print((char)i);
}
fp.close();
}
catch (IOException err) {
System.out.println(err);
}
flag = true;
}
public void run() {
while ( !flag ) {
try {
Thread.sleep( 1000 );
}
catch ( Exception e ) {}
System.out.println( "thread working ..." );
}
}
}
■ プログラムの終了とスレッドの終了
POINT
プログラムが終了するのは, main() スレッドが終了する時ではなく
すべてのユーザースレッドが終了した時に終わる。
スレッドには2種類あり
ユーザースレッドは通常のスレッドの作成でつくられるスレッド。
デーモンスレッドはプログラムが終了するときにスレッドの終了をまつ必要がない。
デーモンスレッドの例として Java の GC がある。
デーモンスレッドにするには, start() の前に setDaemon( true )で設定をする。
public class TestDaemon extends Thread {
public void run() {
while ( true ) {
System.out.println( "daemon thread working" );
}
}
public static void main (String[] args) {
Thread t = new TestDaemon();
t.setDaemon( true );
t.start();
try {
Thread.sleep(1000 * 1);
}
catch ( Exception e ) {}
}
}
■ スレッドの同期(排他)
スレッドは非同期で実行されるが、共有のデータを操作、参照する場合は
矛盾が起きないように処理を同期する必要がある。
つまり相手の動作と合わせる必要があるタイミングがある。
スレッドの同期化とは
1つのリソースに対して2つが同時に操作をしないようにすること。
複数のスレッドで同期をするには モニタを取得する。
スレッド同士を同期する方法は次の2つがある。
synchronized メソッドはメソッドの対象である( this )オブジェクトをロックする。
ロックされた場合、他のスレッドが this に相当するオブジェクトのメソッドを実行しようとすると
処理が待機する。
ロックをしてメソッドの処理が終わったスレッドはロックを解除してメソッドを抜ける。
待機していた別のスレッドはロックをかけて、そのメソッドを開始できる。
オブジェクトが自分のスレッドでロックしているかどうかを調べることもできる
ただし別のスレッドがロックしているかどうかは判断できない。
b = Thread.holdsLock( o );
System.out.println("ロック状態 " + b );
ロックは変数ではなく、その参照先のオブジェクトに対してされる。
synchronized ( o ) {
}
// これも同じ。 holdsLock() でチェックできる。
Object a = o;
synchronized ( o ) {
system.out.println( Thread.holdsLock( a ) );
}
class Toilet
{
synchronized public void enter() {
String s = Thread.currentThread().getName();
System.out.print( s );
for( int j=0; j< 50; j++ ){
System.out.print( "=");
}
System.out.print( ">\n");
}
}
class Human extends Thread
{
Toilet t;
Human( Toilet t ) {
this.t = t;
}
public void run() {
while (true) {
t.enter();
}
}
}
public class TestSync extends Thread
{
public static void main (String[] args) {
Toilet t = new Toilet();
Human h = new Human(t);
h.setName( "foo" );
h.start();
Human h1 = new Human(t);
h1.setName( "bar" );
h1.start();
}
}
■ ロックをすることは書き込み禁止とは異なる
POINT
ロック/アンロックはモニタの取得、解放を意味するだけ。
オブジェクトの変数の設定はできてしまう。
■ タイマーを使って画像を定期的にきりかえる
■ デッドロック
POINT
複数のスレッドがお互いがロックしている
オブジェクトをロックしようとするとデッドロックが発生する。
デッドロックを回避するには、ロックする順番を決めておくとよい。
デッドロックを起こす要因として, suspend() がある。
オブジェクトをロックした状態で suspend() をするとデッドロックの可能性があるため
利用しないほうがよい。
stop() はロック中のオブジェクトを解放するが、
処理が任意のタイミングで中断されるため、作成途中のオブジェクトができるため
利用しない方がいい。
■ スレッドを連携させる
スレッド間で連携した処理をするには 待機 と 通知 を使う。
あるスレッドで変更があった場合に、別のスレッド側へ通知をしてもらう。
その間は待機することで、無駄な時間( CPU 割あて )をなくすことができる。
オブジェクトの変更があるまで, 待機して変更があったら通知してもらうことで
無駄な待ち時間や処理が不要になる。
notify()/notifyAll() を利用する。
wait() メソッドでオブジェクトの状態が変更されるまで一時停止して待ち
POINT
wait() する側は状態の変更を待つ側
オブジェクトの状態が変わるまで待ちたい場合は, wait() を使う。
notifyAll() を使うと、 obj に対して待機しているすべてのスレッドに通知される。
public class TestWait extends Thread
{
boolean flag = false;
static Object o = new Object();
public static void main (String[] args) {
TestWait t = new TestWait();
t.start();
try {
Thread.sleep( 5000 );
}
catch ( Exception e ) {}
System.out.println("変更があったので、通知します。");
try {
Thread.sleep( 1000 );
}
catch ( Exception e ) {}
// notify() するスレッドは、wait() と同じく obj をロックする必要がある。
synchronized( o ) {
o.notify();
}
}
public void run() {
try {
Thread.sleep( 3000 );
}
catch ( Exception e ) {}
// wait() を実行するスレッドは obj をロックする必要がある。
// ロックをしないと、 java.lang.IllegalMonitorStateException が発生して wait() に失敗する。
synchronized( o ) {
try {
System.out.println("待機しています。");
o.wait();
}
catch ( Exception e ) {
System.out.println("throw");
}
}
System.out.println( "thread working ..." );
}
}
wait() はタイムアウトの指定もできる。
notify() がなくても指定時間が過ぎたらスレッドを再開する。
■ キーボード入力したコマンドラインをスレッド側で実行する
public class TestWait2 extends Thread
{
static StringBuffer o = new StringBuffer();
public static void main (String[] args) {
TestWait2 t = new TestWait2();
t.start();
int i = 0;
InputStreamReader fp = new InputStreamReader(System.in);
try {
Thread.sleep( 500 );
}
catch ( Exception e ) {}
while (true) {
try {
Thread.sleep( 1000 );
}
catch ( Exception e ) {}
System.out.println("文字を入力してください。");
StringBuffer st = new StringBuffer();
try {
while(true) {
i = fp.read();
if (i == '\n') break;
System.out.print((char)i);
st.append( (char)i );
}
//fp.close();
}
catch (IOException err) {
System.out.println(err);
}
o.delete( 0, o.length() );
o.append( st );
// notify() するスレッドは、wait() と同じく obj をロックする必要がある。
System.out.println("変更があったので通知します。");
synchronized( o ) {
o.notify();
}
}
}
// うけとった文字列を cmdline として実行する
void cmdlineJob( String s ) {
try {
String t = Thread.currentThread().getName();
System.out.println( t + "cmdlineJob = " + s );
Runtime runtime = Runtime.getRuntime();
Process p = runtime.exec( s );
InputStream stdIn = p.getInputStream();
int c;
while ( (c = stdIn.read()) != -1 ) {
System.out.print((char)c);
}
stdIn.close();
int ret = p.waitFor();
System.out.println("retcode = " + ret);
}
catch ( Exception e ) {
System.out.println("fail");
}
}
// スレッド側では キーボード文字の入力があった場合に処理を実行をする。
public void run() {
while ( true ) {
String s = Thread.currentThread().getName();
// wait() を実行するスレッドは obj をロックする必要がある。
// ロックをしないと、 java.lang.IllegalMonitorStateException が発生して wait() に失敗する。
synchronized( o ) {
try {
System.out.println( s + "待機しています。");
o.wait();
}
catch ( Exception e ) {
System.out.println("throw");
}
cmdlineJob( o.toString() );
}
}
}
}
■ スレッドの優先度をつける
スレッドの優先度をつけるには Thread.setPriority() を使う。
スケジューラは実行可能状態のスレッドのうち、優先度の高いものを実行する。
優先度は 1 - 10 で指定して、値が大きいほど優先度は高い。
POINT
マルチコアの環境では、実際に複数同時に実行できるため
コア数より少ないスレッドの場合は数が同じくらいになる可能性がある。
カウントを表示して優先度の効果を確かめてみる。
// スレッドへのお仕事
class Job implements Runnable
{
volatile public int cnt = 0;
// スレッドに実行させるメソッド
public void run() {
System.out.println("do job" );
while ( true ) {
cnt ++;
}
}
}
public class TestPriority {
public static void main (String[] args) {
ArrayList< Job > a = new ArrayList< Job >();
int nr = 10;
int i = 0;
for( i=0; i< nr; i++ ){
Job job = new Job();
Thread t = new Thread( job, "t1" );
t.setPriority( (i%10) + 1 );
t.start();
a.add( job );
}
while ( true ) {
try {
Thread.sleep(3000 * 1);
}
catch ( Exception e ) {}
for( i=0; i< nr; i++ ){
System.out.println( " job " + (i+1)%10 + " " + a.get(i).cnt );
}
}
}
}
■ スレッドの処理を割り込む
待機中( wait(), sleep(), join() )のスレッドの処理に強制的に再開するには interrupt() を使う。
これによって メインスレッドが終了する時点で,
待機中のスレッドを停止させることができる。