Jakarta ee javaservlet中的竞争条件

Jakarta ee javaservlet中的竞争条件,jakarta-ee,servlets,race-condition,Jakarta Ee,Servlets,Race Condition,我有一个奇怪的问题,两个独立的用户将图片上传到我的web服务,user1看到了user2的错误消息。查看代码时,我没有发现任何问题,因此我想问一下这段代码有什么问题,为什么它会造成这样一种情况,即user2的错误对user1可见 下面是一些简化的代码来尝试和演示这种情况 public class SomeService { private static SomeService service; private SomeService() { } public

我有一个奇怪的问题,两个独立的用户将图片上传到我的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());
        }
    }
}
公共类服务{
私人服务;
私人服务(){
}
公共静态服务getInstance(){
if(服务==null){
service=newsomeservice();
}
}
公共错误状态剂量测量(错误状态es){
es=新的错误状态(es);
//导致错误的事情发生了
添加消息(新的错误消息(“发生了一些错误”);
返回es;
}
公共错误状态doSomethingElse(错误状态es){
es=新的错误状态(es);
//导致错误的事情发生了
添加消息(新的错误消息(“发生了一些不同的错误”);
返回es;
}
}
公共类错误消息{
字符串消息;
//简单的构造函数,getter和setter,没什么有趣的
}
公共类错误状态{
int-id;
字符串状态;
列出信息;
公共错误状态(){
id=0;
地位=”;
messages=newarraylist();
}
公共错误状态(其他错误状态){
id=other.getId();
status=other.getStatus();
messages=other.getMessages();
}
公共无效添加消息(错误消息消息){
//数据检查
消息。添加(消息);
}
//接球手和接球手
}   
公共类UploadServlet扩展了HttpServlet{
公共数据集(请求、响应){
ErrorStatus es=新的ErrorStatus();
SomeService=SomeService.getInstance();
es=服务剂量(es);
es=服务剂量(es);
打印错误(response.getWriter(),es);
}
公共无效打印错误(PrintWriter pw,ErrorStatus es){
对于(int i=0;i
我认为代码中的两个地方可能会发生奇怪的事情,即复制构造函数或服务是单例的事实。也许我没有正确地复制列表,或者可能服务作为一个单例改变了堆栈和堆的使用方式,我真的不确定。从我对堆栈、堆、单例和servlet如何工作的理解来看,一个用户的数据永远不会受到另一个用户数据的影响。 此外,他们唯一遇到问题的部分是列表,原始数据总是正确的,只有错误的用户出现了错误列表


我应该注意到,我已经解决了这个问题,我只是不明白为什么这是个问题。解决方案是停止使用复制构造函数,只允许在doSomething和doSomethingElse方法中修改ErrorStatus对象。因此,修改后的代码将有一个doSomething的void返回类型,并且只调用es.addMessage,同时复制构造函数也从ErrorStatus中删除


如果您能帮助理解这导致比赛状态的原因,我们将不胜感激。

我认为您的问题在于:

messages=other.getMessages()

基本上这意味着两个不同的
ErrorStatus
对象共享相同的
List
,对一个列表的修改会影响另一个列表,反之亦然。这条线应该是这样的

messages=newarraylist(other.getMessages())


这样,列表(最初)将包含相同的元素,但不会受到修改的副作用。

我同意其中一个回答,即很难从简化的代码中找出问题所在。我注意到一些打字错误:

public static SomeService getInstance() {
    if(service == null) {
       service = new SomeService();
    }
    **return service;**
}

public doGet(request, response) {

    ErrorStatus es = new ErrorStatus();
    SomeService service = SomeService.getInstance();

    es = service.doSomething(es); //<-- This first one never gets tracked; es is overwritten below.

    es = service.doSomethingElse(es); 

    printErrors(response.getWriter(), es);

}

public ErrorStatus() {
     id = 0; **<-- How exactly does id increase?**
     status = ""; 
     messages = new ArrayList<>();
}
publicstaticsomeservice getInstance(){
if(服务==null){
service=newsomeservice();
}
**回程服务**
}
公共数据集(请求、响应){
ErrorStatus es=新的ErrorStatus();
SomeService=SomeService.getInstance();
es=service.doSomething(es);//记录错误(le)->读取错误(re)

两个用户可以交错操作

用户1:gsi-->da-->le-->re

用户2:\uuugsi-->da-->le-->re

因此,当user1读取错误时,实际上是user2读取错误,user2刚刚完成错误日志记录。您的UploadServlet可能有一个成员ErrorStatus对象:这将产生问题

祝你好运


这里有一个很好的教程:

您发布的代码片段绝对正确。您确定
SomeService
没有任何实例变量吗?在这种情况下,实例变量将在所有
SomeService
客户端之间共享,我一直非常小心,在服务中没有类级变量,所有的范围都是t方法中使用了它。但是,在两个完全不同的会话之间,不同的用户从不同的机器上点击服务器,如何“泄漏”数据?我理解这可能会导致一个用户出错,我只是不理解我的数据是如何在会话和线程之间泄漏的。可能是您无意中共享了一些ob跨会话的对象,例如通过servlet或某个静态属性。您粘贴的代码看起来都是正确的,但我认为这只是您现有代码的一个简化版本。无论如何,我想知道我的建议是否有任何不同。没有静态属性,示例仅在使用其中包括ErrorStatus对象。ErrorStatus对象是servlet的本地对象,它在doGet方法中实例化,在examp中不在doGet范围之外的任何地方使用