6月5日

スレッドの同期

スレッドを同期しないと、値の代入などが期待通りに動かない場合がある。
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("まだ実装されていません");
	}
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です