javaee并发&;锁定

javaee并发&;锁定,java,jakarta-ee,concurrency,locking,Java,Jakarta Ee,Concurrency,Locking,我有一个MDB(消息驱动bean),它接收带有表示单词的字符串的消息。数据库中还有一个表。MDB应在表中存储字和每个字的接收次数(计数器) 问题在于,为了获得更好的性能,MDB在许多实例中启动,当不同实例接收到相同的新词时,它们都会创建计数为1的同一行 为了解决这个问题,我应该使单词字段唯一,然后第二个实例将在提交时失败,重新传输消息,这将起作用,但可能会有问题。这是一种好的做法吗 另一种解决方案是在计数器求和之后合并这些行。但是如果另一个实例将在更新过程中增加计数器,那将如何? 如果有两个实例

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

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

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

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

如果有两个实例试图增加计数器,该怎么办<代码>@Version应该足够了吗

我不确定这里的正确解决方案是什么。你将如何处理此类案件

您还可以推荐一些关于并发实践的书籍吗(不是关于
synchronized
的使用,因为我需要支持JavaEE并可能运行一个应用服务器集群)


更新: 在阅读了更多关于EJB和JPA的内容之后,我想我想要一个类似于锁定实体的东西。例如,我可以创建一个新表,其中只包含id和键列以及如下所示的数据:

ID | KEY
1  | WORDS_CREATE_LOCK
因此,当我需要处理一个新词时,我会这样做(不是精确的代码,甚至不确定它是否会编译):

//主函数
public void handleWord(字符串wordStr){
Word w=getWord(wordStr);
如果(w==null)
w=getNewOrSychronizedWord(wordStr);
em.lock(w);
w、 setCounter(w.getCounter()+1);
em.unlock(w);
}
//如果未找到,则返回单词instance或null
私有字getWord(字符串字str){
单词w=null;
Query Query=em.createQuery(“从单词中选择w作为w,其中w.string=:wordStr按w.id asc排序”);
setParameter(“wordStr”,wordStr);
List words=query.getResultList();
if(words.getSize()>0)
w=单词。get(0);
返回w;
}
//处理锁定以防止重复创建单词
专用字getNewOrSynchronizedWord(字符串字STR){
单词w=null;
Locks l=em.find(单词\u CREATE\u LOCK\u ID,Locks.class);
em.lock(l);
Word w=getWord(wordStr);
如果(w==null){
w=新词(wordStr);
em.persist(w);
}
em.unlock(l);
返回w;
}
那么问题是,它会这样工作吗?我可以在不维护带有锁定行的DB表的情况下执行同样的操作吗?可能是某种Java EE容器锁定机制?

如果有用的话,我正在使用JBoss4.2


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

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

第二个MDB只允许一个实例,将连续处理消息并允许创建新单词

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

那有多好


谢谢。

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


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

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


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

如果您希望获得性能、无锁定等。我建议使用另一个表:(word、timestamp)。MDB将只插入单词和时间戳。另一个进程将计数并用总数更新表格

可重复读取的隔离级别不是解决方案,因为当两个实例对同一个不存在的字运行select查询时,它们都返回零行,并且没有任何内容被锁定。之后,每个实例将使用相同的单词进行插入。SERIALIZABLE隔离级别更好,因为它会用新词锁定不存在的行(不确定),但这对性能有很大影响,而且Oracle DB可能会有问题。我指的是不同消息中的同一个词,它是队列MDB。“另一种观点”很好,但是有没有一种J2EE方法可以在MDB上执行选择器呢?我知道我可以使用选择器MDB,它将转发到更具体的MDB。但我必须再次确保特定MDB只有一个实例(如何在集群中工作?)@Vitaly Polonetsky-选择器是MDB部署的一个配置方面,它允许的实例数(线程的并发会话)也是如此。您可以多次部署同一个MDB(不同的EAR),但只需为每个MDB配置不同的选择器,并将其配置为仅允许一个会话。@Robin谢谢,但我可以在同一MDB上使用一个选择器实例和多个选择器吗?或者我必须有和选择器一样多的课程?@Vitaly Polonetsky-不知道你的意思。每个部署的MDB只能有一个选择器,因为它是部署描述符配置项,但可以多次部署同一个MDB,每个MDB都有自己的选择器和JNDI名称。这消除了编码多个MDB的需要(因为它们都做完全相同的事情),但允许您
// 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;
}