プログラミング応用b 第10回 『スレッド1』


【スレッドの基本事項】

Fig.1-(1)にスレッドの基礎事項を示す。
  スレッドは, Thread クラスを継承した自分独自のクラスとして定義する必要があり,実際の
処理内容は run( ) というメソッドとして書く(Fig.1-(1)(a))。こうやって定義したクラスは,
「スレッドクラス」などと呼ばれる。

 スレッドを実行するには,前準備として自分で定義したスレッドクラスのオブジェクトを生成
する(Fig.1-(1)(b))。こうして生成されたスレッドクラス型のオブジェクトは,「スレッドオブジェクト」
などと呼ばれる。スレッドオブジェクトの start( ) メソッドを実行することで,スレッドの処理
( run( ) メソッドの中身) が実行開始となる。 start( ) メソッドを実行しても, start( ) メソッド
自体はすぐ終わってしまうので,何も起こっていないように見えるかもしれないが,実際には run( )
メソッドの内容が同時並行的に実行されているのである。




 では,最初から List 1 の動作を見ていこう。最初に,List 1-①と②で,それぞれ "Hello"
と"Java"という文字列をフィールド str に格納した2つの MyThread型オブジェクト t1, t2 を
生成している。

List 1 (再掲)


 List 1-③と④で,t1とt2の start( ) メソッドを呼び出している。この呼び出しによって,
t1とt2の run( ) メソッドの内容(List 1-⑨)が並行的に実行される。もちろん,「最初のスレ
ッド」が実行しているList 1-⑤も並行的に実行されているので,List 1の実行結果で"World",
"Hello","Java"がいりみだれて表示されたのである。

 Fig.2 に, List 1 で3つのスレッドが切り替わりつつ,並行に実行されている様子の例を示す。
まず,「最初のスレッド」から実行が始まり, t1スレッドを生成して, start( ) メソッドを呼び出す
(時間軸①)。そして, t1スレッドの実行が始まる(②)。

  次に,ふたたび「最初のスレッド」に切りかわってt2スレッドの生成され,t2の start( ) メソッド
を呼ぶ(時間軸③)。続いて,t2スレッドの実行が開始される(時間軸④)。以降,スレッ
ドが切り替わりながら,見かけ上,3つのスレッドが並行実行されていく。



 Thread のフィールドとメソッドの概要を参考に見てみたい人はこちら

 なお,List 1 のスレッド処理で表示する文字列データはフィールド str に格納されていて,
コンストラクタで初期化されている。このように,スレッドに対して処理対象となるデータを
フィールド経由で渡すのには理由がある。スレッド処理メソッド run( ) が仮引数を持っていない
ため,そのままではスレッドにデータを渡せないのだ。そのため,スレッドに対して渡すデータ
は,List 1のように,コンストラクタであらかじめフィールドに設定する
のが定石となっている
のである。

 余談だが,List 1-⑤やList 1-⑨の空ループ(何もしないループ)は,互いのスレッドが切り替
わるだけの十分な時間を与えてスレッドの切り替わりを観察するために用意した,学習用の
便宜的な処理
である。実際にスレッドを活用するプログラムを書く際には,このような無駄な
空ループは必要無い。

 


【Runnableインタフェイス】

 ところで,すでにスーパークラスを持っているクラスは,Thread クラスを継承できない。
これは,Javaでは直接のスーパークラスを1個しか持てないからである。こういった場合に,
Thread クラスを継承しなくても,スレッドクラスを作成する手段が用意されている。この
方法は,Runnable インタフェイス(Fig 3)を使う。Runnable インタフェイスは,
Thread クラスと同様に run( ) という抽象メソッドを持っている(Table 1)。それもそのはず。
Fig. 3 に図示されているように, 実は Thread クラスは Runnable を実装していて, run( )
メソッドは Runnable インタフェイスから継承したものなのである。



  Runnable インタフェイスを使ってスレッドを実現するには, Runnable を実装するクラスを
定義し, run( ) メソッドをオーバライドする(Fig.1-(2)(a))。

 そして,実装したクラスのオブジェクトを Thread クラスのコンストラクタに渡して Thread
型のスレッドオブジェクトを生成する(Fig.1-(2)(b))。

 そして, スレッドオブジェクトの start( ) メソッドを呼んでスレッドの実行を開始する(Fig.1-(2)(c))点
は,Threadのサブクラスを作る方法と変わらない。

Fig. 1 (再掲)


 Runnable インタフェイスを実装する方法で,List 1 を書き換えたものを List 2 に示す。
List 2-⑥以下で,List 2-⑨で定義しているAをスーパークラスとし, Runnable を実装し
ている MyRunnable クラスを定義して, runメソッドをオーバライドしている (List 2-⑧)。
 List 2-①と②では, MyRuunable 型オブジェクトをラップするようにして, Thread 型
オブジェクトを生成している。List 2-③と④で, start( ) メソッドを呼んで,スレッドを
開始している。

List 2 MyThreadTest2.java

【 sleep( ) メソッドによる一時停止】

 List 2を見て気づいたかもしれないが,List 1-⑤やList 1-⑨では空ループだった部分
が,List 2-⑤とList 2-⑧では,Thread の staticメソッド sleep( ) を呼ぶように変わって
いる。 sleep( )を呼んだスレッド(すなわり,現在実行中のスレッド)は,整数値で指定された時間
(ミリ秒) だけ,一時停止(スリープ)する。

 通常,何もしないループ(空ループ)は,つねに繰り返し命令を実行しているために CPU のパワー
を無駄に消費している。しかし, sleep( ) メソッドでスリープしている間は,そのスレッドは CPU
のパワーを消費しない
。よって,一定時間の間,実行を中断する場合は,空ループではなく,
sleep( ) を使うようにするべきである。
 なお, List 2 では, sleep( ) メソッドの引数に

  (int) (10 * Mathe.random( ))

という計算式を渡しているが,この式は (10 * Mathe.random( )) の部分で 0.0以上 10.0未満
の double型実数値を求めた上で, 左端の (int) によって小数点以下を切り捨て int型整数値に
変換しているのである。

 ところで, sleep( ) メソッドは InterruptedException 型例外を投げる可能性があるので,適切に
対処しておこう( List 2-⑧では catchブロックを用意することで対処している)。
※どのような時に sleep( ) が InterruptedException 型例外を投げるかは,興味がある人だけ
次のページに掲載されている「興味がある人向け:sleep()などで長時間 停止中の〜(略)」という
ボタンを押して解説を読んでみよう。

次に進む