【问题标题】:Java EE concurrency & lockingJava EE 并发和锁定
【发布时间】:2012-11-26 02:06:44
【问题描述】:

我有一个 MDB(消息驱动 bean),它接收带有字符串的消息,表示一个单词。我也在数据库中有一个表。 MDB 应该在表中存储单词和每个单词被接收的次数(计数器)。

问题在于,为了获得更好的性能,MDB 在许多实例中启动,当不同实例接收到相同的新词时,它们都会创建计数为 1 的同一行。

为了解决这个问题,我应该使单词字段唯一,然后第二个实例将在提交时失败,重新传输消息,这将起作用,但可能会出现问题。这是一个好习惯吗?

另一种解决方案是在对计数器求和之后合并这些行。但是如果另一个实例会在更新过程中增加计数器呢?

如果两个实例试图增加计数器怎么办? @Version 应该够了吧?

我不确定这里的正确解决方案是什么。你会如何处理这种情况?

您能否推荐一些关于并发实践的书籍(不是关于使用synchronized,因为我需要支持Java EE 并且可能运行应用服务器集群)?


更新: 在阅读了有关 EJB 和 JPA 的更多信息后,我想我想要一个锁定实体之类的东西。例如,我可以创建一个只有 id 和 key 列和数据的新表,如下所示:

ID | KEY
1  | WORDS_CREATE_LOCK

所以当我需要处理一个新单词时,我会做这样的事情(不是确切的代码,甚至不确定它是否会编译):

// MAIN FUNCTION
public void handleWord(String wordStr) {
  Word w = getWord(wordStr);

  if (w == null)
    w = getNewOrSychronizedWord(wordStr);

  em.lock(w);
  w.setCounter(w.getCounter() + 1);
  em.unlock(w);
}

// Returns Word instance or null if not found
private Word getWord(String wordStr) {
  Word w = null;

  Query query = em.createQuery("select w from words as w where w.string = :wordStr order by w.id asc");
  query.setParameter("wordStr", wordStr);
  List<Word> words = query.getResultList();

  if (words.getSize() > 0)
    w = words.get(0);

  return w;
}

// Handles locking to prevent duplicate word creation
private Word getNewOrSynchronizedWord(String wordStr) {
  Word w = null;
  Locks l = em.find(WORDS_CREATE_LOCK_ID, Locks.class);
  em.lock(l);

  Word w = getWord(wordStr);

  if (w == null) {
    w = new Word(wordStr);
    em.persist(w);
  }

  em.unlock(l);
  return w;
}

所以问题是它会这样工作吗?我可以在不维护带有锁定行的数据库表的情况下做同样的事情吗?可能是一些 Java EE 容器锁定机制?

如果有帮助,我正在使用 JBoss 4.2。


对此我有一个新想法。我可以创建两个 MDB:

允许多个实例的第一个 MDB,它将处理所有消息,如果找不到该单词,则将该单词发送到第二个 MDB

仅允许一个实例的第二个 MDB,将串行处理消息并允许创建新单词

最好的部分:没有整个表/方法/进程锁定,只有计数器更新时的行锁定

这有多好?

谢谢。

【问题讨论】:

    标签: java jakarta-ee concurrency locking


    【解决方案1】:

    如果您正在寻找性能,无锁定等。我建议您有另一个表:(单词,时间戳)。您的 MDB 将只插入单词和时间戳。另一个进程将计算并使用总数更新表。

    【讨论】:

      【解决方案2】:

      这听起来需要在数据库中通过选择正确的transaction isolation 级别来解决 - 可重复读取应该就足够了。

      你需要一本关于数据库的书,重点是事务。

      【讨论】:

      • REPEATABLE READ 的隔离级别不是解决方案,因为当两个实例对同一个不存在的单词运行选择查询时,它们都返回零行并且没有任何内容被锁定。之后每个实例都会插入相同的单词。 SERIALIZABLE 隔离级别更好,因为它会用新词锁定不存在的行(不确定),但它对性能影响很大,并且可能与 Oracle DB 有问题。
      【解决方案3】:

      您的意思是多个实例正在处理相同的消息,还是在不同的消息中使用相同的词?如果是同一条消息,那么您应该使用队列而不是主题。这当然不能解决多条消息中同一个词的问题。对于这种情况,您可以听从@Michael Borgwardt 和@Vitaly Polonetsky 的建议。

      在数据库之外,另一个选择是让不同的 MDB 实例处理以一组字母开头的单词。这可以通过选择器轻松完成。然后将只有一个 MDB 处理任何特定的单词,但处理仍然在多个实例之间拆分以提高性能。我并不是说这是一个更好的选择,而只是另一种支持非常简单的基于队列的处理。

      【讨论】:

      • 我在不同的消息中指的是同一个词,它是一个队列 MDB。 “另一种意见”很好,但是有没有 J2EE 方法在 MDB 上做选择器?我知道我可以使用将转发到更具体的 MDB 的选择器 MDB。但是我必须再次确保特定的 MDB 只有一个实例(如何?在集群中工作?)。
      • @ Vitaly Polonetsky - 选择器是 MDB 部署的一个配置方面,它允许的实例数量(线程的并发会话)也是如此。您可以多次部署同一个 MDB(不同的 EAR),但只需为每个选择器设置一个不同的选择器并配置为只允许一个会话。
      • @Robin 谢谢,但是我可以通过选择器在同一个 mdb 上使用多个选择器来创建一个实例吗?或者我必须有和选择器一样多的类?
      • @Vitaly Polonetsky - 不确定你的意思。每个部署的 MDB 只能有一个选择器,因为它是一个部署描述符配置项,但是您可以多次部署同一个 MDB,每个都有自己的选择器和 JNDI 名称。这消除了对多个 MDB 进行编码的需要(因为它们都做完全相同的事情),但允许您让每个 MDB 处理不同的消息。
      猜你喜欢
      • 2011-07-24
      • 1970-01-01
      • 1970-01-01
      • 2012-07-15
      • 2015-06-29
      • 1970-01-01
      • 2015-12-24
      • 1970-01-01
      • 2023-03-18
      相关资源
      最近更新 更多