简体   繁体   English

Java并发-Web应用程序

[英]Java Concurrency - Web Application

I think I found more bugs in my web application. 我想我在Web应用程序中发现了更多错误。 Normally, I do not worry about concurrency issues, but when you get a ConcurrentModificationException, you begin to rethink your design. 通常,我并不担心并发问题,但是当您收到ConcurrentModificationException时,您会开始重新考虑设计。

I am using JBoss Seam in conjuction with Hibernate and EHCache on Jetty. 我将JBoss Seam与Jetty上的Hibernate和EHCache结合使用。 Right now, it is a single application server with multiple cores. 现在,它是具有多个核心的单个应用程序服务器。

I briefly looked over my code and found a few places that haven't thrown an exception yet, but I am fairly sure they can. 我简短地检查了一下我的代码,发现了一些尚未引发异常的地方,但是我相当确定它们可以。

The first servlet filter I have basically checks if there are messages to notify the user of an event that occurred in the background (from a job, or another user). 我拥有的第一个servlet过滤器基本上检查是否有消息通知用户后台发生的事件(来自作业或其他用户)。 The filter simply adds messages to the page in a modal popup. 过滤器只是在模式弹出窗口中将消息添加到页面。 The messages are stored on the session context, so it is possible another request could pull the same messages off the session context. 消息存储在会话上下文中,因此可能有另一个请求可以将相同的消息从会话上下文中拉出。

Right now, it works fine, but I am not hitting a page with many concurrent requests. 现在,它工作正常,但是我并没有在页面上看到许多并发请求。 I am thinking that I might need to write some JMeter tests to ensure this doesn't happen. 我认为我可能需要编写一些JMeter测试以确保不会发生这种情况。

The second servlet filter logs all incoming requests along with the session. 第二个Servlet过滤器将所有传入请求与会话一起记录。 This permits me to know where the client is coming from, what browser they're running, etc. The problem I am seeing more recently is on image gallery pages (where there are many requests at about the same time), I end up getting a concurrent modification exception because I'm adding a request to the session. 这使我知道客户端来自何处,正在运行什么浏览器等。最近我看到的问题是在图库页面(大约同时有很多请求)上,我最终得到了并发修改异常,因为我正在向会话添加请求。

The session contains a list of requests, this list appears to be being hit by multiple threads. 该会话包含一个请求列表,该列表似乎被多个线程击中。

@Entity
public class HttpSession 
{
  protected List<HttpRequest> httpRequests;

  @Fetch(FetchMode.SUBSELECT)
  @OneToMany(mappedBy = "httpSession")
  public List<HttpRequest> getHttpRequests()
  {return(httpRequests);}

  ...
}

@Entity
public class HttpRequest
{
  protected HttpSession httpSession;

  @ManyToOne(optional = false)
  @JoinColumn(nullable = false)
  public HttpSession getHttpSession()
  {return(httpSession);}

  ...
}

In that second servlet filter, I am doing something of the sort: 在第二个servlet过滤器中,我正在执行某种操作:

httpSession.getHttpRequests().add(httpRequest);
session.saveOrUpdate(httpSession);

The part that errors out is when I do some comparison to see what changed from request to request: 出错的部分是当我进行比较以查看请求之间的变化时:

for(HttpRequest httpRequest:httpSession.getHttpRequests())

That line there blows up with a concurrent modification exception. 那里的那一行炸毁了并发的修改异常。

Things to walk away with: 1. Will JMeter tests be useful here? 需要走的事情:1. JMeter测试在这里有用吗? 2. What books do you recommend for writing web applications that scale under concurrent load? 2.对于编写在并发负载下可扩展的Web应用程序,您推荐哪些书? 3. I tried placing synchronized around where I think I need it, ie on the method that loops through the requests, but it still fails. 3.我尝试将同步放置在我认为需要的地方,即在遍历请求的方法上放置,但仍然失败。 What else might I need to do? 我还需要做什么?

I added some comments: 我添加了一些评论:

I had thought about making the logging of the http requests a background task. 我曾考虑过使http请求的日志记录成为后台任务。 I can easily spawn a background task to save that information. 我可以轻松地产生一个后台任务来保存该信息。 I am trying to remember why I didn't evaluate that too much. 我想记住为什么我没有对它进行太多评估。 I think there is some information that I would like to have access to on the spot. 我认为我想当场获得一些信息。

If I made it asynchronous, that would speed up the throughput quite a bit - well I'd have to use JMeter to measure those differences. 如果我将其设为异步,那将大大提高吞吐量-我不得不使用JMeter来衡量这些差异。

I would still have to deal with the concurrency issue there. 我仍然必须在那里处理并发问题。

Thanks, 谢谢,

Walter 沃尔特

A ConcurrentModificationException occurrs when any collection is modified while iterating over it. 迭代迭代任何集合时发生ConcurrentModificationException。 You can do it in a single thread, eg: 您可以在一个线程中完成它,例如:

for( Object o : someList ) {
  someList.add( new Object() );
}

Wrap your list with Collections.synchronizedList or return an unmodifiable copy of the list. Collections.synchronizedList包装列表,或者返回列表的不可修改的副本。

  1. I'm not sure about scaling web applications in particular, but Java Concurrency in Practice is a fantastic book on concurrency in general. 我不确定要特别扩展Web应用程序,但是Java Concurrency in Practice实际上是一本关于并发性的绝妙书籍。
  2. The list should be replaced with a version that is threadsafe, or all access to it has to be synchronized (readers and writers) on the same object. 该列表应替换为线程安全的版本,否则必须在同一对象上同步对它的所有访问(读取器和写入器)。 It is not enough to synchronize just the method that reads from the list. 仅同步从列表中读取的方法是不够的。

It's been caused because the list has been modified by another request while you're still iterating over it in one request. 造成这种情况的原因是,您仍在一个请求中对其进行迭代时,该列表已被另一个请求修改。 Replacing the List by ConcurrentLinkedQueue (click link to see javadoc) should fix the particular problem. 通过ConcurrentLinkedQueue替换List (单击链接以查看javadoc)应该可以解决特定问题。

As to your other questions: 关于您的其他问题:

1: Will JMeter tests be useful here? 1:JMeter测试在这里有用吗?

Yes, it is certainly useful to stress-test webapplications and spot concurrency bugs. 是的,对Web应用程序进行压力测试并发现并发错误肯定是有用的。

2: What books do you recommend for writing web applications that scale under concurrent load? 2:对于编写可在并发负载下扩展的Web应用程序,您推荐哪些书?

Not specific tied to webapplications, but more to concurrency in general, the book Concurrency in Practice is the most recommended one in that area. 与并发无关的不是特定于Web应用程序的,而是与并发有关的,一般而言,《 并发实践 》一书是该领域中最受欢迎的一本书。 You can perfectly apply the learned things on webapplications as well, they are a perfect real world example of "heavy concurrent" applications. 您也可以将学到的东西完美地应用到Web应用程序上,它们是“大量并发”应用程序的真实示例。

3: I tried placing synchronized around where I think I need it, ie on the method that loops through the requests, but it still fails. 3:我尝试将同步放置在我认为需要的地方,即在遍历请求的方法上放置,但仍然失败。 What else might I need to do? 我还需要做什么?

You basically need to synchronize any access to the list on the same lock. 基本上,您需要同步对同一锁上的列表的所有访问。 But just replacing by ConcurrentLinkedQueue is easier. 但是只用ConcurrentLinkedQueue替换就更容易了。

You're getting an exception on the iterator, because another thread is altering the collection backing the iterator while you're in mid-iteration. 您在迭代器上遇到异常,因为处于中间迭代状态时,另一个线程正在更改支持迭代器的集合。

  • You could wrap access to the list in synchronized access (both adding and iterating) but there are problems with this, as it could take significantly longer to iterate through a list, along with the processing that goes along with it, and you'd be holding the lock to the list for all of that time. 您可以将对列表的访问权包装在同步访问权中(添加和迭代),但是这样做存在问题,因为遍历列表以及随之而来的处理可能要花费更长的时间。一直保持锁定到列表中。

  • Another option would be to copy the list and pass out the copy for iteration, which might be a better idea if the objects are small, as you'd only be holding the lock while you make the copy, rather than while you're iterating through the list. 另一种选择是复制列表并传递副本以进行迭代,如果对象较小,则可能是一个更好的主意,因为您只会在制作副本时按住锁,而不是在进行迭代时通过列表。

  • Store your values in a ConcurrentHashMap, which uses lock striping to minimize lock contention. 将值存储在ConcurrentHashMap中,该映射使用锁条带化以最小化锁争用。 You could then have your get method return a copied list of the keys you want, rather than the complete objects, and access them one at at time directly from the map. 然后,您可以让get方法返回所需键的复制列表,而不是完整的对象,然后一次从地图直接访问它们。

As is mentioned in another answer here, Java Concurrency in Practice is a great book. 正如在这里另一个答案中提到,Java并发实践是一个伟大的书。

The other posters are correct in stating that you need to be writing to a threadsafe data structure. 其他说明者正确地说,您需要写入线程安全数据结构。 In doing so, you may slow down your response time due to thread contention. 这样做可能会由于线程争用而减慢响应时间。 Since this essentially a logging operation that is a side effect of the request itself (Or am I not understanding you correctly?) you could spawn a new thread responsible for writing to the threadsafe data structure. 由于这本质上是一个日志记录操作,它是请求本身的副作用(或者我是否理解不正确?),因此可以生成一个新线程来负责写入线程安全数据结构。 That allows you to proceed with the actual response instead of burning response time on a logging operation. 这样一来,您可以继续实际的响应,而不必在记录操作中消耗响应时间。 It might be worth investigating setting up a threadpool to reduce the time required to use the logging threads. 可能值得研究设置线程池以减少使用日志记录线程所需的时间。

Any concurrency book by Doug Lea is worth reading. Doug Lea撰写的任何并发书籍都值得一读。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM