ソフト ウェア演習
C目 次
第10章Java(5) { 例外処理,マルチスレッド 110.1
第10
回演習内容 : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :1
10.2
例外処理: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :1
10.2.1
例外 : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :1
10.2.2
例外処理: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :2
10.3
マルチスレッド : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :4
10.3.1
Threadクラス : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :4
10.3.2
Runnableインタフェース : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :7
10.3.3
同期処理: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :8
10.4
課題 : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :10
第
10章
Java(5) {例外処理
,マルチスレッド
10.1第
10回演習内容
第10
回では、プログラム実行中に発生するさまざ まな問題に対処するための例外処理と、複数の処理を 同時に実行させることのできるマルチスレッド についての演習を行います。 第10
回目の演習内容は以下の通りです。 例外処理 マルチスレッド 10.2例外処理
10.2.1例外
プログラムの実行中、たとえば 、ある数値をゼロで除算した、配列の添字の指定が不正だった、指定し たファイルが見つからなかったなど 、さまざ まな問題が発生する場合があります。Java
では、このような 問題を通知するために、例外(exception)と呼ばれるオブジェクトを実行時に生成します。 まず、以下のプログラムを実行してください。 // ExceptionTest.java // ExceptionTestクラスpublic class ExceptionTest { // コンストラクタ ExceptionTest() { array() // arrayメソッド 呼出 } // arrayメソッド void array() { int ] ary = { 0, 1, 2, 3, 4 } // 配列変数定義 System.out.println( ary5] ) // これはエラーになる } // メイン メソッド
public static void main( String ] args ) { // ExceptionTestクラスのインスタンスを作成
ExceptionTest expTest = new ExceptionTest() }
}
このプログラムでは、まずメイン メソッド でExceptionTestクラスのインスタンスを作成しています。
ExceptionTestクラスのコンストラクタでは、arrayメソッド を呼び出しており、arrayメソッド では、
定義されていないのに対し 、定義されていない
6
番目の要素を参照しようとしているため、プログラムは エラーとなります。実際にプログラムを実行してみると、
$ java ExceptionTest
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at ExceptionTest.array(ExceptionTest.java:13) at ExceptionTest.<init>(ExceptionTest.java:7) at ExceptionTest.main(ExceptionTest.java:19) と表示されます。つまり、ここでは定義されていない配列の要素を参照しようとしたとして、 ArrayIndexOutOfBoundsExceptionという例外オブジェクトが作成され 、異常終了したことを示していま す。このように、プログラム実行時になんらかの問題が生じた場合、例外オブジェクトが作成され通知し ますが 、これを「例外が投げられる」といいます。
Java
では、例外はExceptionクラスとして定義されており、また、さまざ まな種類の例外がExceptionクラスのサブ クラスとし て定義され ています。その中で 、最も頻繁に使用され 、最も重要なクラスは RuntimeExceptionクラスで、プログラム中に発生する頻度が高い例外を定義しています。 RuntimeExceptionクラスにはさらにサブ クラスがあり、その中の使用頻度の高いものを表
10.1
に示し ます。 表10.1:
RuntimeExceptionのサブ クラス クラス 説明 ArrayIndexOutOfBoundsExcetion 配列の添字に実在しない要素番号を指定した ArithmeticException 算術例外が発生した(ゼロで除算したなど) ClassCastException 不正なキャスト操作を試みた NegativeArraySizeException 配列のサイズとして負の値を使用した NullPointerException 空のオブジェクトの変数やメソッド にアクセスしようとした NumberFormatException 文字列を数値に変換するときの文字列の形式が不正だった SecurityException セキュリティ違反のため操作が拒否された StringIndexOutofBoundsException 文字列のインデックスが文字列の領域をはみ出した演習
10-1 以下のプログラムは3
種類の例外が投げられる可能性がある。それぞれどのような場合にどのような例 外が投げられるかを挙げよ。 // ArgDiv.java public class ArgDiv {public static void main( String ] args ) {
int val1 = Integer.parseInt( args0] ) // 文字列をint型に変換
int val2 = Integer.parseInt( args1] )
System.out.println( val1 + " / " + val2 + " = " + val1/val2 ) }
}
10.2.2
例外処理
通常はプログラム実行中に発生した例外は自動的に処理されますが 、
Java
では投げられた例外を、ユーことにより行います。構文は以下のようになります。
try {
// 例外が投げられる可能性のある処理
}
catch (ExceptionType1 param1) { // ExceptionType1に対応する例外処理
}
catch (ExceptionType2 param2) { // ExceptionType2に対応する例外処理
} ...
catch (ExceptionTypeN paramN) { // ExceptionTypeNに対応する例外処理 } finally { // finallyブロック } まず、tryブロックでは、例外が投げられる可能性のある処理を記述します。次に、いくつかcatchブ ロックが続きます。ここでは、tryブロックで投げられる可能性のある例外オブジェクトを引数として与 え、実際に例外が投げられたときの処理を記述します。catchブロックは複数記述することができ、引数と して与えた例外オブジェクトに一致しない場合は次のcatchブロックに検索が進みます。finallyブロッ クは、例外発生の有無に関わらず処理される処理を記述します。このfinallyブロックは、省略可能です。 以下に、前のサンプルExceptionTest.javaに例外処理を加えた例を示します。 // ExceptionTest2.java // ExceptionTest2クラス
public class ExceptionTest2 { // コンストラクタ ExceptionTest2() { array() // arrayメソッド 呼出 } // arrayメソッド void array() { int ] ary = { 0, 1, 2, 3, 4 } // 配列変数定義 // try文の中に例外が投げられる可能性のある処理を書く try { System.out.println( ary5] ) // ここで例外が投げられる } // 投げられる可能性のある例外オブジェクトを指定 catch( ArrayIndexOutOfBoundsException be ) { System.out.println( "配列の添字が不正です" ) } // その他の例外の場合 catch( Exception e ) { System.out.println( "その他の例外" ) e.printStackTrace() // 例外情報を表示 }
(
次のページに続く)
// ここは必ず処理される finally { System.out.println( "finally内の処理" ) } System.out.println( "try文の外" ) } // メイン メソッド
public static void main( String ] args ) { // ExceptionTestクラスのインスタンスを作成
ExceptionTest2 expTest2 = new ExceptionTest2() } } この例の場合、まず、ArrayIndexOutOfBoundsExceptionクラスのオブジェクトをcatchの引数とし ていますので、ArrayIndexOutOfBoundsExceptionが投げられた場合はこのブロックの処理が実行されま す。もし 、これ以外の例外が投げられた場合は次のcatch文を検索します。なお、
2
つ目のcatch文では、 Exceptionクラスのオブジェクトを引数としていますが 、この場合は全ての例外に一致することになりま す。また、このブロックのprintStackTraceメソッド は、Exceptionクラスの、例外発生時の情報を表 示するためのメソッド です。なお、これまでの例のような、RuntimeExceptionの場合は、特にプログラ ム内で例外処理を行わなくてもインタープ リタが処理してくれます。しかし 、RuntimeException以外の 例外を投げる可能性のあるメソッド1 を使用する場合はtry∼catch文で例外処理を行う必要があります。演習
10-2 演習10-1
で示したプログラムArgDiv.javaに、それぞれの例外に対応する例外処理を追加せよ。なお、 例外処理は、それぞれの例外に対応するメッセージを標準出力に出力することにより行うこと。 10.3マルチスレッド
プログラムは、例えばmainメソッドの最初の処理から始まり、1
文ずつ逐次的に実行されます。そして 終点を見つけると、そこで処理を終了します。プログラムの作りによっては複雑な処理の流れに見える場 合もありますが 、大局的に見ると通常は1
本の流れとなっています。スレッド (Thread)とは、プログラ ムにおけるこのような処理の流れを意味します。マルチスレッド のプログラムとは、このスレッド を同時 に複数実行できる事を意味します。Java
はマルチスレッド をサポートし 、1
つのプログラム中で複数の処 理を同時に実行できます。例えば 、あるスレッドには画面の制御、またあるスレッドには音楽の処理といっ たように記述でき、作成された各スレッド はそれぞれ自分の処理を専属的に実行します。これは、Java
の ように標準でマルチスレッド を想定していないプログラム言語では、作業の配分等をプログラマ自身が細 かく設定し 、複雑なプログラムを書かねばならないことと比較すると有効な機能といえます。 10.3.1 Threadクラス
スレッド を作成するもっとも基礎的な方法は、java.lang.Threadクラスを継承する方法です。Thread クラスにはいくつかのメソッドが用意されています。ここでは、代表的なメソッド を紹介します。 1Threadクラスのsleepメソッド や、BufferedReaderクラスのreadLineメソッド など 。それぞれのメソッドについては次節 以降で扱います。
Threadクラスのインスタンスに対してstartメソッドを呼び出すと、そのインスタンスのrunメソッド
を実行する新しいスレッドが作成されます。runメソッド は通常のメソッドとしても機能しますが 、main
メソッド のようにスレッド の処理が始まったときに自動的に呼ばれます。runメソッド の実行が終了した
時点でそのスレッド は消滅します。図
10.1
にこの概略を示します。main() Thread t Thread u
u.start() t.start() run() run() 図
10.1:
スレッド また、2
つのスレッドを作成する例を以下のプログラムThreadTest.javaに示します。 // MyThread.javapublic class MyThread extends Thread { // Thread クラスを継承
String mes // メッセージ
long sleeptime // 中断時間(msec)
public MyThread( String mes, long sleeptime ) { // コンストラクタ
this.mes = mes
this.sleeptime = sleeptime }
public void run() { // runメソッド をオーバーライド
for( int i = 0 i < 10 i++ ) { System.out.println( mes+i ) try {
Thread.sleep( sleeptime ) // sleeptimeの間、処理を中断
} catch( InterruptedException ie ) { ie.printStackTrace() } } } }
// ThreadTest.java public class ThreadTest {
public static void main( String ] args ) {
Thread t = new MyThread( "Hello Java. ", 1000 ) // MyThreadのインスタンス
Thread u = new MyThread( "Hello world. ", 2000 ) u.start() // startメソッド
t.start()
System.out.println( "Main thread is over." ) } } このプログラムの実行結果は以下のようになります。 $ javac ThreadTest.java $ java ThreadTest Hello world. 0 Main thread is over. Hello Java. 0 Hello Java. 1 Hello world. 1 Hello Java. 2 Hello Java. 3 Hello world. 2 Hello Java. 4 Hello Java. 5 Hello world. 3 Hello Java. 6 Hello Java. 7 Hello world. 4 Hello Java. 8 Hello Java. 9 Hello world. 5 Hello world. 6 Hello world. 7 Hello world. 8 Hello world. 9 このプログラムではMyThreadクラスのインスタンスを
2
つ作成し 、startメソッドを呼び出します。作 成された各インスタンスでは、第1
引数で与えられた文字列を表示した後、第2
引数で指定された時間の 間処理を中断します。この処理をそれぞれ独立したスレッドで10
回行います。また、このプログラムで、 sleepメソッド は、Threadクラスの静的メソッド で、引数で与えた時間(
ミリ秒)
だけ処理を中断します。 つまり、この場合、1
秒毎に文字列を表示するスレッドと、2
秒毎に文字列を表示するスレッドが同時に処 理されます。スレッド 毎にCPU
を専属的に配分できないコンピュータでは、システムが各スレッド の処理 をCPU
に適当に割り当てます。このため、このプ ログラムの実行結果はシステムの内部状態に依存し常 に同じ結果を出力するとは限りません。なお、sleepメソッド は、InterruptedExceptionを投げる可能 性があるため、try∼catch文による例外処理を行う必要があります。演習
10-33
つのスレッド を持つプログラムを作成せよ。ここで、1
つ目はアルファベット(A
∼Z)
を順に1
秒毎に 表示するスレッド、2
つ目は数字(0
∼9)
を順に3
秒毎に表示するスレッド、3
つ目はひらがな(
あ∼ん)
を 順に0.5
秒毎に表示するスレッド とする。なお、ここでは表示するメソッドとしてSystem.out.print() を使用するものとする。10.3.2 Runnable
インタフェース
Java
は単一継承であるため、Threadクラスを継承した場合は他のクラスを継承できません。例えば 、画 面にフレームを作成するスレッドを作りたい場合、JFrameクラス2 を継承する方法をとりますが 、これで はThreadクラスの継承はできません。このため、Runnableインタフェースが用意されており、これを実 装する事によりあるクラスを継承しつつ独立したスレッド として機能させる事ができます。 // MyRunnable.javapublic class MyRunnable implements Runnable { // Runnableインタフェースを実装
String mes // メッセージ
long sleeptime // 中断時間(msec)
public MyRunnable( String mes, long sleeptime ) { // コンストラクタ
this.mes = mes
this.sleeptime = sleeptime }
public void run() { // runメソッド をオーバーライド
for( int i = 0 i < 10 i++ ){ System.out.println( mes+i ) try {
Thread.sleep( sleeptime ) // sleeptimeの間、処理を中断
} catch( InterruptedException ie ) { ie.printStackTrace() } } } } // RunnableTest.java
public class RunnableTest {
public static void main( String ] args ) {
Runnable cmd1 = new MyRunnable( "Hello Java. ", 1000 ) // MyRunnableのインスタンス
Runnable cmd2 = new MyRunnable( "Hello world. ", 2000 ) Thread t = new Thread( cmd1 ) // Threadのインスタンス
Thread u = new Thread( cmd2 ) t.start() // startメソッド
u.start()
System.out.println( "Main thread is over." ) }
}
Threadクラスを継承する方法と同様に、複数のスレッドを作成することが確認できます。このように、ス
レッドでの処理をRunnableインタフェースを実装したクラスのrunメソッドに記述し 、それをRunnable
クラスのインスタンスとして作成し 、さらにそれをThreadクラスのコンストラクタに引数として渡すこ とで、他のクラスを継承したオブジェクトを処理するスレッド を作成することができます。
演習
10-4 演習10-3
を、Runnableインタフェースを実装する方法で書き換えよ。 2 JFrameクラスについては、次回以降の演習で行います。10.3.3
同期処理
スレッドはそれぞれ独立して処理を実行するので、どのスレッドがどのオブジェクトのデータをどの時 点で参照あるいは利用するかといったことは予測できません。あるスレッドが 、あるオブジェクトのデー タを変更している最中に、他のスレッドがそのオブジェクトのデータを参照したとすると正しいデータの 参照ができない場合があります。処理を厳密に行うためには、複数のスレッド 間で処理のタイミングを取 り合う必要があり、これを同期をとるといいます。Java
では、あるメソッド に対して同時に複数のスレッドがそのメソッドを参照しないようにするための 予約語synchronizedが用意されています。これを宣言したメソッドは同時に1
つのスレッドからのみア クセスを受け付けます。ここで、もし複数のスレッドが 、synchronized宣言されたメソッド を参照しよ うとした場合、1
つのスレッド のみがアクセスできるので他のスレッド はアクセス中のスレッドがそのメ ソッド へのアクセスを終了するまで待機状態になります。Java
では、これらの待機状態のスレッド を連続 的に監視しており、synchronized宣言されたメソッド を持つオブジェクトのデータの状態をプログラマ が管理する必要はありません。 // Increment.java public class Increment {int v
void calc( String s, long sleeptime ) { // インクリメント用のメソッド
for( int i = 0 i < 10 i++ ) { v++
System.out.println( v + " :" + s ) try {
Thread.sleep( sleeptime ) // sleeptimeの間、処理を中断
} catch( InterruptedException ie ) { ie.printStackTrace() } } } } // ExecIncrement.java
public class ExecIncrement implements Runnable { Increment inc // Incrementのインスタンス
String t_name // スレッド の名前
long sleeptime // 中断時間
ExecIncrement( Increment inc, String t_name, long sleeptime ) { // コンストラクタ
this.inc = inc this.t_name = t_name this.sleeptime = sleeptime }
public void run() { // runメソッド をオーバーライド
inc.calc( t_name, sleeptime ) }
}
// SyncTest.java
// Incrementクラスのフィールドvの値を2つのスレッド で加算し 、20にする
public class SyncTest {
public static void main( String ] args ) { Increment inc = new Increment()
Thread t1 = new Thread( new ExecIncrement( inc, "Thread 1", 1000 ) ) Thread t2 = new Thread( new ExecIncrement( inc, "Thread 2", 1500 ) ) t1.start() t2.start() } } この例のIncrementクラスのcalcメソッド は、フィールド vを
10
回インクリメントするメソッド です。 ExecIncrementクラスは、スレッドを作成するために、Runnableインタフェースを実装したクラスと して作成しています。 SyncTestクラスでは、Incrementクラスのインスタン ス1
つに対し 、2
つのスレッド を作成し 、実行 させています。2
つのスレッドが同時に動いているので、2
つのスレッドが入り混じりながらフィールド v をインクリメントする結果となります。そのため、処理系によっては2
つのスレッドが同時にインクリメ ントしてしまう可能性があり、もし同時にインクリメントしてしまうと、期待通りの結果にはならない場 合があります。それを防ぐ ため、Incrementクラスのcalcメソッド をsynchronized宣言し 、複数のス レッドが同時にアクセスできないようにします。Incrementクラスのcalcメソッド を
synchronized void calc( String s, long sleeptime ) {
と書き換えて実行してみてください。この場合、
1
つのスレッドが10
までインクリメントした後に、もう1
つのスレッドが20
までインクリメントするようになります。演習
10-5 演習10-4
を、表示中にそれぞれの文字種が混ざらないように改造せよ。たとえば 、アルファベットを全 て表示させてから数字を表示させ 、数字を全て表示させてからひらがなを表示させるようにする。なお、 このとき、表示する文字種の順序は問わないものとする。10.4
課題
必須課題については必ずレポートを提出してください。自由課題にもぜひ挑戦してみましょう。プログ ラムを作成する課題については、作成したソースプログラムおよびその実行に必要なソースプログラムと、 その実行結果を添付してください。また、作成したウィンド ウの画面イメージも添付してください。 2 必須課題1.
以下のプログラムは3
種類の例外が投げられる可能性がある。それぞれどのような場合にどのような 例外が投げられるかを挙げ(演習10-1)、それぞれの例外に対応する例外処理をプログラムに追加せ よ。なお、例外処理は、それぞれの例外に対応するメッセージを標準出力に出力することにより行う こと。(演習10-2) // ArgDiv.java public class ArgDiv {public static void main( String ] args ) {
int val1 = Integer.parseInt( args0] ) // 文字列をint型に変換
int val2 = Integer.parseInt( args1] )
System.out.println( val1 + " / " + val2 + " = " + val1/val2 ) } }
2. 3
つのスレッドを持つプログラムを作成せよ。ここで、1
つ目はアルファベット(A
∼Z)
を順に1
秒毎に表 示するスレッド、2
つ目は数字(0
∼9)
を順に3
秒毎に表示するスレッド、3
つ目はひらがな(
あ∼ん)
を順 に0.5
秒毎に表示するスレッドとする。なお、ここでは表示するメソッドとしてSystem.out.print() を使用するものとする。(演習10-3)3.
必須課題2
を、Runnableインタフェースを実装する方法で書き換えよ。(演習10-4)4.
必須課題3
を、表示中にそれぞれの文字種が混ざらないように改造せよ。たとえば 、アルファベット を全て表示させてから数字を表示させ、数字を全て表示させてからひらがなを表示させるようにする。 なお、このとき、表示する文字種の順序は問わないものとする。(演習10-5) 2 自由課題1. Java
では、発生した例外を呼び出し元のメソッド に知らせることができ、呼び出し元のメソッド でも 例外処理を行うことができる。この機能について調べ、テキスト内のExceptionTest2.javaについ て、ExceptionTest2コンストラクタでも例外処理を行うようにプログラムを変更せよ。2. Java
では、独自の例外を作成し 、処理することができる。この機能について調べ、Math.random()メ ソッド を利用して0
∼99
の乱数を10
個発生させ、乱数が10
以下の場合に例外を発生させるプログラ ムを作成せよ。3.
マルチスレッドにおけるデッド ロックとはどのような状態のことか、どのようなときに起こり得るか、 また、それを防ぐにはどのようにすればよいか調べて述べよ。参考文献
1) 「3
日で解るJava
」,
共立出版,
桑原 恒夫 著2) 「
JAVA
実践プログラミング 」,
オライリージャパン, Patrick Niemeyer & Joshua Peck
著,
安藤進 訳明広 訳
4) 「
Java
プログラムデザイン 」,
ソフトバンク,
戸松豊和 著5) 「
JAVA
プログラムクイックリファレンス」,
オライリージャパン, David Flanagan
著,
豊福剛 訳 6) 「JAVA
入門」,
インプレス, Nathan Gurewich & Ori Gurewich
著,
石川和也 訳7) 「
Internet Language Java
入門」,
技術評論社,
河西朝雄 著8) 「
Java
言語入門」,
プレンティスホール, Laura Lemay, & Charles L. Perkins
著,
武舎広幸, &
久野禎子
, &
久野靖 訳9) 「基礎からの