防止消息处理的竞争条件

时间:2011-10-04 11:48:47

标签: java java-ee

我有一个J2EE应用程序,它通过Web服务接收消息(事件)。消息具有不同类型(根据类型需要不同的处理)并以特定顺序发送。它已经确定了一些问题,其中某些消息类型需要比其他消息类型更长结果是序列中第二个接收的消息可以在序列中的第一个之前被处理。我试图通过在处理消息的方法周围放置一个synchronized块来解决这个问题。这似乎有效,但我不相信这是“正确的”方法吗?是否有可能更合适的替代方案或“可接受”?我已经包含了一小段代码,试图更清楚地解释。 ....任何建议/指导赞赏。

public class EventServiceImpl implements EventService {
  public String submit (String msg) {

    if (msg == null)
        return ("NAK");

            EventQueue.getInstance().submit(msg);

    return "ACK";
  }
}


public class EventQueue {
    private static EventQueue instance = null;
    private static int QUEUE_LENGTH = 10000;
    protected boolean done = false;
    BlockingQueue<String> myQueue = new LinkedBlockingQueue<String>(QUEUE_LENGTH);

protected EventQueue() {
    new Thread(new Consumer(myQueue)).start();
}

public static EventQueue getInstance() {
      if(instance == null) {
         instance = new EventQueue();
      }
      return instance;
}

public void submit(String event) {
    try {
        myQueue.put(event);
    } catch (InterruptedException ex) {
    }
}

class Consumer implements Runnable {
    protected BlockingQueue<String> queue;

    Consumer(BlockingQueue<String> theQueue) { this.queue = theQueue; }

    public void run() {
      try {
        while (true) {
          Object obj = queue.take();
          process(obj);
          if (done) {
            return;
          }
        }
      } catch (InterruptedException ex) {
      }
    }

    void process(Object obj) {
        Event event = new Event( (String) obj);
        EventHandler handler = EventHandlerFactory.getInstance(event);
        handler.execute();
    }
}

// Close queue gracefully
public void close() {
    this.done = true;
}

2 个答案:

答案 0 :(得分:4)

我不确定您正在使用的框架(EJB(MDB)/ JMS)是什么。通常应该避免在 Managed Environment 中使用像EJB / JMS那样的同步(这不是一个好习惯)。一种解决方法是

  • 客户端应在发送下一条消息之前等待服务器的确认。
  • 这样客户端本身就会控制事件的顺序。

请注意,如果有多个客户提交邮件,则无法使用此功能。

编辑:

您遇到这样的情况:Web服务的客户端按顺序发送消息而不考虑消息处理时间。它只是一个接一个地转储消息。这是基于Queue ( First In First Out )的解决方案的一个很好的案例。我建议用两种方法来完成这个

  1. 使用JMS。这将增加JMS providers并编写一些管道代码会产生额外的开销。
  2. 使用一些多头模式,例如Producer-Consumer,其中您的Web服务处理程序将传入的消息转储到队列中,单线程使用者将一次消耗一条消息。请参阅使用java.util.concurrent包的this example
  3. 使用数据库。将传入的消息转储到数据库中。使用不同的基于调度程序的程序扫描数据库(基于序列号)并相应地处理消息。

    第一和第三种解决方案对于这些类型的问题非常标准。第二种方法很快,代码中不需要任何额外的库。

答案 1 :(得分:2)

如果要按特定顺序处理事件,那么为什么不尝试在消息中添加“eventID”和“orderID”字段?这样,您的EventServiceImpl类可以按正确的顺序排序,排序然后执行(无论它们是否被创建和/或传递给处理程序)。

我预计,同步handler.execute()块将无法获得所需的结果。所有synchronized关键字都会阻止多个线程同时执行该块。它在正确排序下一个线程的范围内什么也没做。

如果synchronized块似乎确实有效,那么我断言你正在变得非常幸运,因为正在创建,传递消息,然后以正确的顺序执行操作。在多线程环境中,这是不确定的!我会采取措施确保你控制住这一点,而不是依靠好运。

示例:

  1. 根据'client01-A','client01-C'的顺序创建消息, 'client01-B','client01-D'
  2. 邮件按“client01-D”的顺序到达处理程序, 'client01-B','client01-A','client01-C'
  3. EventHandler可以区分从一个客户端到另一个客户端的消息,并开始缓存“client01”的消息。
  4. EventHandler recv的'client01-A'消息,知道它可以处理这个并且这样做。
  5. EventHandler在缓存中查找消息'client01-B',查找并处理它。
  6. EventHandler找不到'client01-C',因为它尚未到达。
  7. EventHandler recv的'client01-C'并对其进行处理。
  8. EventHandler在缓存中查找'client01-D'找到它,处理它,并认为'client01'交互完成。
  9. 这些方面的某些内容可确保正确处理并促进多线程的良好使用。