オブジェクト指向プログラミング演習 2010/10/27
演習課題
スレッド(その 2) 同期処理、結果不正、デッドロック
前回のスレッドの演習では、複数のスレッドを実行し、 一つのプログラムの中の違う処理を同時に実行し た。
ただし、無作為にスレッドを複数実行すると、結果不正やデッドロックが起きる可能性がある。
複数のスレッド(マルチスレッド)を安全に実行する(スレッドセーフにする)ためには、同期処理を用いる こ とが必要になる。同期処理は、予約語 synchronized で行うことができる。 ここでは、synchronized につ
課題 1
以下のプログラム Kadai101001 を複数回実行してみると、結果が違う場合がある。 /* スレッドの結果不正をテストする */
public class Kadai102701{
public static void main(String[] args){
SimpleThreadTest simpleThreadTest = new SimpleThreadTest(); Thread thread = new Thread(simpleThreadTest);
thread.start(); //simpleThreadTest.run()が呼ばれ,その後 add()を呼ぶ simpleThreadTest.add(); //同時にもう一回 add()を呼ぶ
} }
class SimpleThreadTest implements Runnable{ int sum; //合計を保持するインスタンス変数
/* Thread.start()から呼ばれる */ public void run(){
this.add(); //add()を呼ぶだけ }
/* 0 + 100 を行うだけ */ public void add(){
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} sum = 0; //sum を 0 にして //sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){}
sum = sum + 100; // sum に 100 足す
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} System.out.println("0+100 ="+sum); //0+100 は 100 のはず? } }
実行例 メソッドを synchronized 宣言することにより、そのメソッドが複数のスレッド から同時に呼ばれないように することができる。 上記例では、メソッド dangerousMethod() を synchronized 宣言することにより、 メソッド dangerousMethod() は複数のスレッドから同時に呼ばれない。 (後に呼んだスレッドは、前のスレッドがメ ソッドを抜けるまで待つ。) プログラム Kadai102701 のメソッドに synchronized 宣言を追加し、0+100=100 と常に正しく表示される ように 修正せよ。 課題 2 synchronized はメソッド全体のロックだけでなく、 特定のブロックだけをロックすることもできる。 synchronized( オブジェクト ) { ブロック } 上記では、オブジェクトをロックし、ブロック内を安全に処理する。 別のスレッドで、同じオブジェクト(鍵)を ロックする synchronized 処理は同時に 実行されず、ブロック内の処理を完了するまで待たされる。 例) $ java Kadai102701 0+100 =100 0+100 =200 $ java Kadai102701 0+100 =200 0+100 =200 $ java Kadai102701 0+100 =0 0+100 =100 $
public synchronized void dangerousMethod(){ ...
}
synchronized( this ) { //this をロックオブジェクトに利用する例 dangerousData += 100;
... }
以下のプログラム Kadai102702 を複数回実行してみると、結果が違う場合がある。 /* スレッドの結果不正をテストする */
public class Kadai102702{
public static void main(String[] args){
SimpleThreadTest simpleThreadTest = new SimpleThreadTest(); Thread thread = new Thread(simpleThreadTest);
thread.start(); //simpleThreadTest.run()が呼ばれ,その後 add()を呼ぶ simpleThreadTest.subtract(); //同時に subtract()を呼ぶ
} }
class SimpleThreadTest implements Runnable{ int sum; //合計を保持するインスタンス変数
/* Thread.start()から呼ばれる */ public void run(){
this.add(); //add()を呼ぶだけ }
/* 0 + 100 を行うだけ */ public void add(){
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} sum = 0; //sum を 0 にして //sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){}
sum = sum + 100; // sum に 100 足す
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} System.out.println("0+100 ="+sum); //0+100 は 100 のはず? }
実行例
プログラム Kadai102702 のメソッド add() と subtract() 両方に synchronized ブロック処理を追加し、 /* 1000 - 500 を行うだけ */
public void subtract(){
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} sum = 1000; //sum を 1000 にして //sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){}
sum = sum - 500; // sum に 100 足す
//sleep()でちょっと一休み。結果不正を出しやすくする。 try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException ie){} System.out.println("1000-500 ="+sum); //1000-500 は 500 のはず? } } $ java Kadai102702 1000-500 =500 0+100 =600 $ java Kadai102702 1000-500 =-400 0+100 =-400 $ java Kadai102702 1000-500 =100 0+100 =100 $ java Kadai102702 0+100 =600 1000-500 =600 $
課題 3 タイピングゲームなどは、自分がタイプしている最中にも、非同期的に(同時に) コンピュータが時間を計 って動作することにより、リアルタイムな操作性を実現している。 以下のタイピングゲームをスレッド処理 を用いて非同期で実行できるよう完成させなさい。 import java.io.*; /* タイピングゲームもどき */ public class Kadai102703{
public static void main(String[] args){
Typing typing = new ******(); //Typing クラスの生成 Thread thread = ******(typing); //Thread クラスの生成
System.out.println("以下の文章を間違わずにタイプしください。"); try{
Thread.sleep(1000);
}catch(InterruptedException ie){}
String string = "Now is the time for all good men to come to the aid of their country."; System.out.println(string); ******.start(); //スレッド開始 typing.type(string); } }
class Typing implements Runnable{
Object lock = new ******(); //共通ロックオブジェクトを生成 boolean fired = false;
/* Thread.start()から呼ばれる */ public void run(){
this.timer(); //timer()を呼ぶだけ }
public void timer(){ //sleep()で時間を待つ try{ Thread.sleep(10000); }catch(InterruptedException ie){} synchronized(****){ //共通のロックオブジェクト if(fired == true) { System.out.println("あなたの勝ち!"); }else{ System.out.println("ドーン! あなたの負け!"); fired = true; } } }
public void type(String string){ String line="";
try{
BufferedReader buf=new BufferedReader(new InputStreamReader(System.in)); line = buf.readLine();
}catch(IOException ioe){}
synchronized(****){ //共通のロックオブジェクト if(line.equals(string) && (fired == false)){
System.out.print("バーン!"); fired=true; } } } }