スレッドの同期
スレッドを同期しないと、値の代入などが期待通りに動かない場合がある。
DojiSample.java では、countに1をプラスすることを2つのスレッドから同時に行うことで、誤った結果となる礼を示している。
DojiSample.java
package c25; public class DojiSample { Integer count = new Integer(1); void countUp() { int tmpValue = count; tmpValue = tmpValue + 1; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count = tmpValue; } class CountThread extends Thread { public void run() { countUp(); System.out.println(getName() + " count=" + count); } } void doSample() { new CountThread().start(); new CountThread().start(); } public static void main(String[] args) { new DojiSample().doSample(); } }
DojiSample.javaを実行した結果は以下のようになる。
Thread-0 count=2 Thread-1 count=2
count変数に同時に複数のスレッドからアクセスできないようにするために、synchronizedブロックを使用する。
DojiSample.java
void countUp() { synchronized (count) { int tmpValue = count; tmpValue = tmpValue + 1; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count = tmpValue; } }
synchronizedブロック内でカウントアップする処理を行うようにすることで、countがきちんとカウントアップされるようになる。
Thread-0 count=2 Thread-1 count=3
練習問題
テキストp.508の問題3を入力して実行してみる。
Renshu3.java
class BarThread extends Thread { StringBuffer sb; public BarThread(StringBuffer sb) { this.sb = sb; } public void run() { addNextChar(); } void addNextChar() { char lastChar = sb.charAt(sb.length() - 1); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sb.append((char)(lastChar + 1)); System.out.println(sb.toString()); } } public class Renshu3 { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("a"); for (int i = 0; i < 10; i++) { BarThread t = new BarThread(sb); t.start(); } } }
実行結果(例)
abbbb abbbbbbbbbb abbbbbbbbb abbbbbbbbb abbbbbbb abbbbbbb abbbbb abbbb abbbb abbbb
addNextChar() メソッドの内部を synchronized(sb) ブロック内に入れることで、期待通りに動作するようになる。
Renshu3.java
void addNextChar() { synchronized (sb) { char lastChar = sb.charAt(sb.length() - 1); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sb.append((char)(lastChar + 1)); System.out.println(sb.toString()); } }
出力結果は以下のとおり。
ab abc abcd abcde abcdef abcdefg abcdefgh abcdefghi abcdefghij abcdefghijk
例題
Renshu3.java の出力内容を、hoge.txt に出力する。
mainメソッド内でPrintWriterのインスタンスを生成し、BarThreadのコンストラクタに渡すようにする。
BarThreadでは、受け取ったPrintWriterに対して文字列を出力する。
Renshu3.java
import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; class BarThread extends Thread { StringBuffer sb; PrintWriter out; public BarThread(StringBuffer sb, PrintWriter out) { this.sb = sb; this.out = out; } public void run() { addNextChar(); } void addNextChar() { synchronized (sb) { char lastChar = sb.charAt(sb.length() - 1); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sb.append((char)(lastChar + 1)); System.out.println(sb.toString()); out.println(sb.toString()); } } } public class Renshu3 { public static void main(String[] args) throws IOException { PrintWriter out = new PrintWriter(new FileWriter("hoge.txt")); StringBuffer sb = new StringBuffer(); sb.append("a"); for (int i = 0; i < 10; i++) { BarThread t = new BarThread(sb, out); t.start(); } } }
ファイルには何も書かれていない。
それは、BarThreadがファイルに書き込む前にmainスレッドが終了し、出力先のファイルがクローズされてしまうため。
mainスレッドの終了を Thread.sleep() で遅延させれば書き込みが行われる。
Renshu3.java
public class Renshu3 { public static void main(String[] args) throws IOException { PrintWriter out = new PrintWriter(new FileWriter("hoge.txt")); StringBuffer sb = new StringBuffer(); sb.append("a"); for (int i = 0; i < 10; i++) { BarThread t = new BarThread(sb, out); t.start(); } try { Thread.sleep(2000); out.close(); } catch (InterruptedException e) { e.printStackTrace(); } } }
作成した hoge.txt を読み込むためのクラス TextDocument クラスを作る。
TextDocument.java
public class TextDocument { }
TextDocumentの単体テストクラスを作る。
パッケージエクスプローラでTextDocumentクラスを右クリックし、[新規]-[JUnitテストケース]を選択する。
TextDocumentTest.java
import static org.junit.Assert.*; import org.junit.Test; public class TextDocumentTest { @Test public void test() { fail("まだ実装されていません"); } }