2 人の別々のユーザーが私の Web サービスに画像をアップロードし、user1 が user2 のエラー メッセージを見るという奇妙な問題があります。コードを確認しても何も思い浮かばないので、このコードの何が問題なのか、なぜ user2 のエラーが user1 に表示されるこの状態を作成したのかを尋ねたいと思います。
これは、状況を試して実証するための単純化されたコードです。
public class SomeService {
private static SomeService service;
private SomeService() {
}
public static SomeService getInstance() {
if(service == null) {
service = new SomeService();
}
}
public ErrorStatus doSomething(ErrorStatus es) {
es = new ErrorStatus(es);
// stuff happens that causes an error
es.addMessage(new ErrorMessage("some error happened"));
return es;
}
public ErrorStatus doSomethingElse(ErrorStatus es) {
es = new ErrorStatus(es);
// stuff happens that causes an error
es.addMessage(new ErrorMessage("some different error happened"));
return es;
}
}
public class ErrorMessage {
String message;
//simple constructor, getters and setters, nothing interesting
}
public class ErrorStatus {
int id;
String status;
List<ErrorMessage> messages;
public ErrorStatus() {
id = 0;
status = "";
messages = new ArrayList<>();
}
public ErrorStatus(ErrorStatus other) {
id = other.getId();
status = other.getStatus();
messages = other.getMessages();
}
public void addMessage(ErrorMessage message) {
//data checks
messages.add(message);
}
//getters and setters
}
public class UploadServlet extends HttpServlet {
public doGet(request, response) {
ErrorStatus es = new ErrorStatus();
SomeService service = SomeService.getInstance();
es = service.doSomething(es);
es = service.doSomethingElse(es);
printErrors(response.getWriter(), es);
}
public void printErrors(PrintWriter pw, ErrorStatus es) {
for(int i = 0; i < es.getMessages().size(); i++) {
pw.write(es.getMessages().get(i).getMessage());
}
}
}
コード内で奇妙なことが起こっている可能性があると思われる 2 つの場所は、コピー コンストラクター、またはサービスがシングルトンであるという事実です。リストを正しくコピーしていないか、シングルトンであるサービスがスタックとヒープの使用方法を変更している可能性がありますが、よくわかりません。スタック、ヒープ、シングルトン、およびサーブレットがどのように機能するかについての私の理解から、あるユーザーのデータが別のユーザーのデータの影響を受けることは決してありません。また、これまでに問題があったのはリストの一部だけでした。プリミティブ データは常に正しく、エラーのリストだけが間違ったユーザーに表示されていました。
私は問題を解決しましたが、なぜそれが問題なのか理解できませんでした。解決策は、コピー コンストラクターの使用を停止し、ErrorStatus オブジェクトが doSomething および doSomethingElse メソッドで変更されるようにすることでした。そのため、改訂されたコードでは、doSomething の戻り値の型が void になり、es.addMessage を呼び出すだけになり、コピー コンストラクターも ErrorStatus から削除されました。
これが競合状態を引き起こした理由を理解する助けがあれば幸いです。