プロが教えるわが家の防犯対策術!

VM:java1.4.2
OS:WindowsXp

マルチスレッドのプログラムで、一つのファイルにテキストの出力を行うところで、うまくいかないところがあります。

<ソースファイル>
import java.io.*;
import java.util.*;
import java.text.*;

public class ThreadIppai {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread thread = new ThreadHontai();
thread.start();
}
}
}

class ThreadHontai extends Thread {
public void run() {
try {
for (int i = 0; i < 500; i++) {
BufferedWriter bw = new BufferedWriter(new FileWriter(
"D:\\out.log", true));
String msg = (String) Values.ht.get(String.valueOf((int) (Math.random() * 10)).substring(0, 1)) + "\n";
bw.write(msg, 0, msg.length());
bw.flush();
bw.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Values {
public static Hashtable ht = new Hashtable();
static {
//ht.put("0", "0000000000");
//ht.put("1", "1111111111");
//ht.put("2", "2222222222");
//ht.put("3", "3333333333");
//ht.put("4", "4444444444");
//ht.put("5", "5555555555");
//ht.put("6", "6666666666");
//ht.put("7", "7777777777");
//ht.put("8", "8888888888");
//ht.put("9", "9999999999");
ht.put("0", "0");
ht.put("1", "11");
ht.put("2", "222");
ht.put("3", "3333");
ht.put("4", "44444");
ht.put("5", "555555");
ht.put("6", "6666666");
ht.put("7", "77777777");
ht.put("8", "888888888");
ht.put("9", "9999999999");
}
}

<問題点>
ファイルに出力された結果をみると、テキストの一部が欠けていたり、改行がされない行があったりします。
おそらく、同期処理を加えてないからだとは思うのですが・・・。(質問に続く)

<質問1>
テキストの一部が欠けたり、改行されない行が発生する原因はなぜでしょうか?
たとえば、「0」と「11」を出力するとき、同時に複数のスレッドが書き込んだ場合、「101」となるのは、なんとなく分かります。
しかし、これが「01」のように、出力されるべき文字が出力されないという現象が発生してます。

<質問2>
htにputする値の文字列長が、すべて異なっていますが、これをコメントアウトされている行のように、すべて同じ文字列長に
した場合、上記の問題は発生しなくなります。
この原因はなんでしょうか?

<質問3>
この問題を、ThreadHontaiクラスのfor文の中だけの変更で解決することは可能でしょうか?(極力手を加えずに)
synchronizedブロックの追加でいけるのかと思いましたが、試行錯誤の結果うまくいきませんでした。

以上、よろしくお願いします。

A 回答 (4件)

素人です。


勘ですが・・・。

■質問(1)(2)について
>これが「01」のように、
>出力されるべき文字が出力されないという現象

「競合」が起こっている以上、
とりあえずは、(実装に応じた)どんな不具合も起こりうる気がします。
が、たとえば仮に、
「ファイル追記処理」の実装が、
(i)(現在の"ファイル書き込み位置"から)文字列を書き込む
(ii)"ファイル書き込み位置"(ファイル末尾)を更新する
という"2段構え"なっていたとします。
そして今、2つのスレッドA,Bが同時に、
同じ"ファイル書き込み位置"pから、
スレッドAは"11\n"を、スレッドBは"44444\n"を書き込もうとしているとします。
ここで、
ア:(B-i)(A-i)(A-ii)(B-ii)の順で処理が発生すれば、
最終的にファイルに追記される文字列は"11\n44\n"。
イ:(B-i)(A-i)(B-ii)(A-ii)の順で処理が発生すれば、
最終的にファイルに追記される文字列は"11\n"。(Bによる書き込みが完全につぶれる)
ウ:(A-i)(B-i)(B-ii)(A-ii)の順で処理が発生すれば、
最終的にファイルに追記される文字列は"44\n"。
エ:(A-i)(B-i)(A-ii)(B-ii)の順で処理が発生すれば、
最終的にファイルに追記される文字列は"44444\n"。(Aによる書き込みが完全につぶれる)

イとエは、一見、スレッド競合が発生してないように見えます。


■質問(3)について
synchronized(ThreadHontai.class){
・・・
}
などとすればよいのでは?
    • good
    • 0

よくわからないですが、1つのスレッドの中で500個もBufferedWriterクラスとFileWriterクラスをforループの中で生成しては破棄し、を繰り返しているので、スレッド処理とあいまってメモリ管理がおかしくなってるのかな。

。って気がしますね。
※だから文字列長が同じならBufferedで出力される長さが同じなので大丈夫なってる?

普通は、for文の中だけってのは、難しいですが、外に1個クラスを作れば、限りなくfor文だけの修正にみえなくはないようにはできます(笑)

import java.io.*;
import java.util.*;
import java.text.*;

public class ThreadIppai {
public static void main(String[] args) {
OutLog.init();
for (int i = 0; i < 100; i++) {
Thread thread = new ThreadHontai();
thread.start();
}
OutLog.dispose();
}
}

class ThreadHontai extends Thread {
public void run() {
try {
for (int i = 0; i < 500; i++) {
OutLog.prn();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Values {
public static Hashtable ht = new Hashtable();
static {
// ht.put("0", "0000000000");
// ht.put("1", "1111111111");
// ht.put("2", "2222222222");
// ht.put("3", "3333333333");
// ht.put("4", "4444444444");
// ht.put("5", "5555555555");
// ht.put("6", "6666666666");
// ht.put("7", "7777777777");
// ht.put("8", "8888888888");
// ht.put("9", "9999999999");
ht.put("0", "0");
ht.put("1", "11");
ht.put("2", "222");
ht.put("3", "3333");
ht.put("4", "44444");
ht.put("5", "555555");
ht.put("6", "6666666");
ht.put("7", "77777777");
ht.put("8", "888888888");
ht.put("9", "9999999999");
}
}

public class OutLog{
private static PrintWiter prn = null;
private static boolean init_end = false;
private static String lock = new String("lock");
public static void init(){
synchronized(lock){
if(init_end){
return;
}
prn = new PrintWriter(new BufferedWriter(new FileWriter("D:\\out.log", true)));
init_end = true;
}
return;
}

public static void dispose(){
synchronized(lock){
if(!init_end){
return;
}
prn.close();
init_end = false;
}
}

public static void prn(){
String msg = (String) Values.ht.get(String.valueOf((int) (Math.random() * 10)).substring(0, 1)) + "\n";
prn.write(msg);
prn.flush();
}
}
    • good
    • 0
    • good
    • 0

Javaは初心者なので、まるで分かりません。

試しに <ソースファイル> を自分の環境で実行してみたところ、正常に実行されました。実行環境の問題でしょうかね。
(ちなみに自分の環境)
OS: Windows XP Home SP2
環境: cygwin 1.5.24 (1.5.24(0.156/4/2) 2007-01-23 18:50 i686 Cygwin)
コンパイラ: gcj (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

なお、インスタンスを 1 つのスレッドで実行するすべてのクラスでは、Runnable インタフェースを実装する必要があるらしいので、
synchronizedブロックを追加すると、以下の様なプログラムになるのではないでしょうか。

import java.io.*;
import java.util.*;
import java.text.*;

class Share {
public synchronized void run_module() {
try {
for (int i = 0; i < 500; i++) {
BufferedWriter bw = new BufferedWriter(new FileWriter(
"D:\\out.log", true));
String msg = (String) Values.ht.get(String.valueOf((int) (Math.random() * 10)).substring(0, 1)) + "\n";
bw.write(msg, 0, msg.length());
bw.flush();
bw.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Sync implements Runnable {
Share var;

Sync(Share obj) {
var = obj;
}

public void run() {
var.run_module();
}
}


public class ThreadIppai {
public static void main(String[] args) {
Share obj = new Share();
Thread[] thres = new Thread[100];

for (int i = 0; i < 100; i++) {
thres[i] = new Thread(new Sync(obj));
thres[i].start();
}
}
}

class Values {
public static Hashtable ht = new Hashtable();
static {
// ht.put("0", "0000000000");
// ht.put("1", "1111111111");
// ht.put("2", "2222222222");
// ht.put("3", "3333333333");
// ht.put("4", "4444444444");
// ht.put("5", "5555555555");
// ht.put("6", "6666666666");
// ht.put("7", "7777777777");
// ht.put("8", "8888888888");
// ht.put("9", "9999999999");
ht.put("0", "0");
ht.put("1", "11");
ht.put("2", "222");
ht.put("3", "3333");
ht.put("4", "44444");
ht.put("5", "555555");
ht.put("6", "6666666");
ht.put("7", "77777777");
ht.put("8", "888888888");
ht.put("9", "9999999999");
}
}
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

>自分の環境で実行してみたところ、正常に実行されました。実行環境の問題でしょうかね。
動作テストありがとうございます。
正常に実行されるとは、環境の問題なのかもしれませんね。
ただ、もともとこのプログラムは同期化されないハズだと思って作ったものなので、正常に実行されるとは意外でした。
私は、OSはXPとビスタ、その他の環境は全て同じで3台のPCで試験しましたが、どれも結果は同じでした。(ただ、マシン性能によってなのか、高速なマシンほど出力が乱れました。)

また、せっかく修正ソースを出していただいたのに、申し訳ないのですが、質問3については「ThreadHontaiクラスのfor文の中だけの変更」という箇所が実は重要です。
もし仮に、「ThreadHontaiクラスのfor文の中だけの変更」だけでは技術的に不可能ということであれば、その事実が分かるだけでも構わないと考えています。

お礼日時:2007/07/05 02:28

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!