Java并发-Web应用程序

Java并发-Web应用程序,java,concurrency,seam,Java,Concurrency,Seam,我想我在我的web应用程序中发现了更多的bug。通常,我不担心并发性问题,但当您遇到ConcurrentModificationException时,您就开始重新考虑您的设计 我将JBossSeam与Jetty上的Hibernate和EHCache结合使用。现在,它是一个具有多个核心的单一应用服务器 我简要地查看了我的代码,发现了一些还没有抛出异常的地方,但我确信它们可以 我使用的第一个servlet过滤器基本上检查是否有消息通知用户后台发生的事件(来自作业或其他用户)。过滤器只是在模式弹出窗口

我想我在我的web应用程序中发现了更多的bug。通常,我不担心并发性问题,但当您遇到ConcurrentModificationException时,您就开始重新考虑您的设计

我将JBossSeam与Jetty上的Hibernate和EHCache结合使用。现在,它是一个具有多个核心的单一应用服务器

我简要地查看了我的代码,发现了一些还没有抛出异常的地方,但我确信它们可以

我使用的第一个servlet过滤器基本上检查是否有消息通知用户后台发生的事件(来自作业或其他用户)。过滤器只是在模式弹出窗口中将消息添加到页面中。消息存储在会话上下文中,因此另一个请求可能会从会话上下文中提取相同的消息

现在,它工作得很好,但是我没有碰到一个有很多并发请求的页面。我想我可能需要编写一些JMeter测试来确保这不会发生

第二个servlet过滤器记录所有传入请求以及会话。这使我能够知道客户端来自何处,运行的浏览器是什么,等等。我最近看到的问题是在图像库页面上(几乎同时有许多请求),我最终得到了并发修改异常,因为我正在向会话添加请求

会话包含一个请求列表,该列表似乎被多个线程命中

@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);}

  ...
}
出错的部分是当我进行一些比较以查看请求之间的更改时:

for(HttpRequest httpRequest:httpSession.getHttpRequests())
那一行出现了一个并发的修改异常

要解决的问题: 1.JMeter测试在这里有用吗? 2.对于编写可在并发负载下扩展的web应用程序,您推荐哪些书籍? 3.我试着把synchronized放在我认为需要的地方,即放在循环处理请求的方法上,但仍然失败。我还需要做什么

我补充了一些意见:

我曾考虑将记录http请求作为一项后台任务。我可以很容易地生成一个后台任务来保存这些信息。我在试着回忆为什么我没有对它进行太多的评估。我想有一些信息我想在现场得到

如果我让它异步,这将大大提高吞吐量——我必须使用JMeter来测量这些差异

我仍然需要在那里处理并发性问题

谢谢


Walter

这是由于列表已被另一个请求修改,而您仍在一个请求中对其进行迭代。将
列表
替换为(单击链接查看javadoc)应该可以解决特定的问题

关于你的其他问题:

1:JMeter测试在这里有用吗

是的,对webapplications进行压力测试和发现并发性错误当然是有用的

2:对于编写可在并发负载下扩展的web应用程序,您推荐哪些书籍

这本书不是专门针对webapplications的,而是针对并发性的,它是该领域最受推荐的书。您也可以将学到的知识完美地应用于Web应用程序,它们是“大量并发”应用程序的完美现实示例

3:我试着把synchronized放在我认为需要的地方,即放在循环请求的方法上,但还是失败了。我还需要做什么

基本上,您需要在同一个锁上同步对列表的任何访问。但是仅仅用
ConcurrentLinkedQueue
替换就更容易了

  • 我不确定如何扩展web应用程序,但这是一本关于并发性的好书
  • 该列表应替换为线程安全的版本,或者必须在同一对象上同步对该列表的所有访问(读卡器和写卡器)。仅同步从列表中读取的方法是不够的

  • 当对任何集合进行迭代时修改该集合时,将发生ConcurrentModificationException。您可以在单个线程中执行此操作,例如:

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

    使用列表包装列表或返回列表的不可修改副本。

    迭代器出现异常,因为在迭代过程中,另一个线程正在更改支持迭代器的集合

    • 您可以将对列表的访问包装为同步访问(包括添加和迭代),但这有问题,因为遍历列表以及随之进行的处理可能需要更长的时间,并且您将一直保持对列表的锁定

    • 另一个选项是复制列表并将副本分发给迭代,如果对象很小,这可能是一个更好的主意,因为您只需在创建副本时持有锁,而不是在迭代列表时持有锁

    • 将值存储在ConcurrentHashMap中,该映射使用锁条带化来最小化锁争用。然后,您可以让get方法返回所需键的复制列表,而不是完整的对象,并直接从映射一次访问一个键


    正如在这里的另一个答案中提到的,Java并发实践是一本好书

    其他海报正确地指出您需要写入线程安全数据结构。这样做,可能会由于线程争用而降低响应时间。由于这本质上是一个日志操作,它是请求本身的副作用(或者我没有正确地理解您吗?),因此您可以生成一个负责写入线程安全数据结构的新线程。这允许您继续执行实际响应,而不是在日志记录操作中消耗响应时间。可能值得研究设置线程池以减少
    for( Object o : someList ) {
      someList.add( new Object() );
    }