Jakarta ee javaservlet中的竞争条件
我有一个奇怪的问题,两个独立的用户将图片上传到我的web服务,user1看到了user2的错误消息。查看代码时,我没有发现任何问题,因此我想问一下这段代码有什么问题,为什么它会造成这样一种情况,即user2的错误对user1可见 下面是一些简化的代码来尝试和演示这种情况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
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范围之外的任何地方使用