プログラミングc 第9回 - cdr6275cdr6275.jp/home/wp-content/uploads/2013/06/... ·...
TRANSCRIPT
白石路雄 <[email protected]>
プログラミングC 第9回例外・スレッド
例外
例外:実行時に起きるエラーユーザーが入力した文字列を整数に変換するプログラムを実行した。しかし、ユーザーが整数以外の文字を入力してしまった。ファイルを処理するプログラムで指定したファイルがない。データベースを扱うプログラムでデータベースに接続できない。などなど。
2
例外を処理する (1)
例外を処理するプログラムfinally節はなくてもよい
3
try{ (例外が発生するかもしれない処理)}catch(Exceptionのクラス名 e){ (例外が発生した時の処理)}finally{ (例外の発生の有無に関わらず、必ず行う処理)}
例外を処理する (2)
例外の例Integer.parseInt()は、文字列が構文解析可能な整数型を含まない場合、NumberFormatExceptionを送出する(投げる)。
4
例外を処理する (3)
下記のプログラムで「3.5」と入力してみる
結果
5
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str = br.readLine();int value = Integer.parseInt(str);
Exception in thread "main" java.lang.NumberFormatException: For input string: "3.5"at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.lang.Integer.parseInt(Integer.java:492)at java.lang.Integer.parseInt(Integer.java:527)at NumberFormatExceptionTest.main(NumberFormatExceptionTest.java:10)
例外を処理する (4)
NumberFormatExceptionが起きたときの処理を加える
6
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str = br.readLine();try{ int value = Integer.parseInt(str);}catch(NumberFormatException e){ System.out.println("Error!");}
メソッドでの例外の処理 (1)
キーボードからデータを読み出すクラスを作ってみる
7
public class KeyboardReader{ public int readInteger(){ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str = br.readLine(); int value = 0; try{ value = Integer.parseInt(str); } catch(NumberFormatException e){ System.out.println("Error!"); } return value; }} コンパイルできない
メソッドでの例外の処理 (2)
原因:StringクラスのreadLineメソッドはIOException
を投げる。
8
public class KeyboardReader{ public int readInteger(){ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str = br.readLine(); int value = 0; try{ value = Integer.parseInt(str); } catch(NumberFormatException e){ System.out.println("Error!"); } return value; }}
メソッドでの例外の処理 (2)
対応策1: 先ほどのように自分で処理するtry-catch-finallyを使う
2: メソッドの呼び出し側に処理を任せるthrows節を使う
9
メソッドでの例外の処理 (3)
対応策1: 自分で処理する
10
public class KeyboardReader{ public int readInteger(){ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int value = 0; try{ String str = br.readLine(); value = Integer.parseInt(str); } catch(NumberFormatException e){ System.out.println("Error!"); } catch(IOException e){ System.out.println("Error!"); } return value; }}
メソッドでの例外の処理 (4)
対応策2: メソッドの呼び出し側に処理を任せるthrows節をメソッドの宣言部につける
11
public class KeyboardReader{ public int readInteger() throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str = br.readLine(); int value = 0; try{ value = Integer.parseInt(str); } catch(NumberFormatException e){ System.out.println("Error!"); } return value; }}
例外を定義する
ここまでで他の人が作ったライブラリが投げる例外を処理して使うことができるようになります。自分でライブラリを作る場合には、自分で例外を定義する必要が出てきます。その場合、Exceptionクラスを拡張したクラスを作り、throw
文で例外を投げます。
12
スレッド (1)
これまでのプログラムは基本的に上から順に実行された。処理の流れを増やすことができる。処理の流れをスレッドと呼ぶ。
13
これまでのプログラム
処理1処理2処理3
複数のスレッドによるプログラム
処理1
処理2 処理3
スレッド (2)
スレッドの作り方には2種類ある。1. Threadクラスを拡張したクラスを作る2. Runnableインターフェースを実装する
教科書では両方説明していますが、2の方法のほうが使い勝手がいいので授業では2だけ触れます。
14
スレッド (3)
Runnableインターフェースを実装したクラスを作り、別スレッドで実行したい処理をpublic void
run()メソッドの中に書きます
15
public class Life implements Runnable{ public void run(){ String[] s = {"Eat", "Play", "Nap"}; for(int i=0; i<6; i++){ try{ System.out.println("Life: " + s[i%3]); Thread.sleep(800); } catch(InterruptedException e){ } } }}
スレッド (4)
スレッドを実行するには、1. Runnableインターフェースを実装したクラスのインスタンスを作成する2. そのインスタンスを使ってThreadクラスのインスタンスを作る3. Threadクラスのインスタンスのstart()メソッドを呼び出す
16
スレッド (5)
17
public class Main{ public static void main(String[] args){ Life l = new Life(); Thread t = new Thread(l); t.start(); for(int i=0; i<8; i++){ try{ System.out.println("Main: Work"); Thread.sleep(600); } catch(InterruptedException e){ } } }}
スレッド (6)
18
実行結果
Main: WorkLife: EatMain: WorkLife: PlayMain: WorkLife: NapMain: WorkLife: EatMain: WorkMain: WorkLife: PlayMain: WorkLife: NapMain: Work
スレッド (7)
19
スレッドの実行順はJVMのスケジューラが決定し、制御できないスレッドの動きを制御するには、スレッドの終了を待つjoin()を使う必要がある
スレッド (8)
20
スレッドの終了を待つにはjoin()を使うpublic class Main{ public static void main(String[] args){ Life l = new Life(); Thread t = new Thread(l); t.start(); for(int i=0; i<8; i++){ try{ if(i==4) t.join(); System.out.println("Main: Work"); Thread.sleep(600); } catch(InterruptedException e){ } } }}
スレッド (9)
21
実行結果
Main: WorkLife: EatMain: WorkLife: PlayMain: WorkLife: NapMain: WorkLife: EatLife: PlayLife: NapMain: WorkMain: WorkMain: WorkMain: Work
同期 (1)
22
次のようなクラスを考えますレアカードを提供するサーバで、初期値100枚でconsumeCard()をすると1枚減ります
public class RareCardServer{ public int count = 100; public void consumeCard(){ int currrentCount = count; // データベースへの接続などにかかる時間を // シミュレートするため100ミリ秒停止 try{Thread.sleep(100);} catch(InterruptedException e){ } count = currentCount - 1; }}
同期 (2)
23
次のようなクラスを考えますレアカードを使うスレッド
public class RareCardConsumer implements Runnable{ private RareCardServer server; public RareCardConsumer(RareCardServer server){ this.server = server; } public void run(){ // 50枚適当に寝ながら使う for(int i=0; i<50; i++){ server.consumeCard(); try{Thread.sleep((int)(Math.random()*100));} catch(IntteruptedException e){ } } System.out.println("50枚つかったぜー"); }}
同期 (3)
24
RareCardConsumerを2つ作って同時に走らせてみるpublic class RareCardTest{ public static void main(String[] args){ RareCardServer s = new RareCardServer(); Thread t1 = new Thread(new RareCardConsumer(s)); Thread t2 = new Thread(new RareCardConsumer(s)); t1.start(); t2.start(); try{ t1.join(); t2.join(); } catch(IntteruptedException e){ } System.out.println("Serverに残ったカードの枚数:"+s.count); }}
同期 (4)
25
実行結果
2スレッドで50枚ずつ使ったのだからServerには1枚も残らないはず… (運営涙目)(Serverに残ったカードの枚数は実行するたび変化します)
50枚つかったぜー50枚つかったぜーServerに残ったカードの枚数:47
同期 (5)
26
問題な部分スレッド#1がconsumeCard()を実行するcountは100なので、currentCountを100にして、ちょっと処理する同時にスレッド#2がconsumeCard()を実行するcountは100なので、currentCountを100にして、ちょっと処理する
int currrentCount = count;try{Thread.sleep(100);} catch(InterruptedException e){ } count = currentCount - 1;
同期 (6)
27
問題な部分スレッド#1の先ほどの処理が終わったのでcountに99を書き込むスレッド#2の先ほどの処理が終わったのでcountに99を書き込む2枚使われてるはずなのに1枚しか減ってない (;_;)
int currrentCount = count;try{Thread.sleep(100);} catch(InterruptedException e){ } count = currentCount - 1;
同期 (7)
28
解決策:synchronizedキーワードをつけるそのメソッドを実行しているスレッドが1つであることを保証できる
public class RareCardServer{ public int count = 100; public synchronized void consumeCard(){ int currrentCount = count; // データベースへの接続などにかかる時間を // シミュレートするため100ミリ秒停止 try{Thread.sleep(100);} catch(InterruptedException e){ } count = currentCount - 1; }}
同期 (8)
29
GREE「ドリランド」でレアカード複製技が炸裂 (2012年2月)1枚レアカードを引いたら、2台の携帯を用意して、2つサブ垢作って、同時にカードをあげる処理をするそれぞれのサブ垢でカードをゲットできる→ヤフオクで転売 (よいこはまねしないでね)
レアカードだったらまだいいですけど、お金ならどういうことが起きますか?興味があるひとは「データベース 分離レベル」でgoogle
課題 (1)
30
整数を順に入力し、それらの合計を求めるプログラムExceptionTest.javaを書いてください。ただし、以下の条件を満たすものとします。整数を順に入力する。exitと入力されたら、その時点での和を出力してプログラムを終了する。「3.2」や「exit2」のように整数またはexit以外の入力があった場合には、「整数が正しいフォーマットで入力されませんでした。」と表示する。その場合でもプログラムが終了してはいけません。
課題 (2-1)
31
ファイルが更新されたかどうかを1秒間隔で調べるプログラムを書きます。FileTest.javaをダウンロードして、そこで使用されているFileWatchThreadクラスを完成させてください。動作は次のようになります。最初に指定するファイル名を入力します。スレッドの起動時にファイルのサイズを出力します。ファイルが更新されると、更新されたサイズを出力します。
課題 (2-2)
32
実行例は次のようになります。この例ではまずtest.txtという名前のファイルを作成しています。次にプログラムを実行しながら、test.txtファイルをエディタで更新しています。
監視するファイル名を指定してください> test.txtファイルをチェックしています。最初のサイズは3バイトです。ファイルが更新されました。サイズは7バイトです。ファイルが更新されました。サイズは9バイトです。
課題 (2-3)
33
ヒント
興味のある人は、ファイルの更新時刻を「2013年6月14日14時59分23秒」のように出力する方法について調べてみましょう。
// import java.io.*; として、以下のようにして// ファイルを表すクラスのインスタンスを作成します。File file = new File("test.txt");
// ファイルサイズの取得は以下のように行います。long size = file.length();
// ファイルの更新時刻(「エポック」からの経過時間をミリ秒単位で表したもの)の取得は以下のように行います。long lastModified = file.lastModified();