0

我已经阅读了关于Servlet 线程处理的精彩文章。

我有以下问题:我想创建一个简单的 Servlet,它启动一个在第一个版本中生成随机消息的新线程,或者 - 根据参数 - 发送一个包含自上次请求以来产生的所有消息的响应。

我在浏览器站点上使用 JQuery AJAX 调用处理超时请求。

当我运行接收器调用时,我只收到自线程同时崩溃以来产生的第一条消息。看起来这将是一个线程安全问题,如上述文章中所述,但我可以准确地弄清楚。日志给了我以下信息:

SEVERE: Exception in thread "Thread-75" 
SEVERE: java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at com.lancom.lsr.util.RandomMessageProducer.run(RandomMessageProducer.java:35)
    at java.lang.Thread.run(Thread.java:722)

SEVERE:     at java.lang.Object.wait(Native Method)
SEVERE:     at com.lancom.lsr.util.RandomMessageProducer.run(RandomMessageProducer.java:35)
SEVERE:     at java.lang.Thread.run(Thread.java:722)

这是我当前的 servlet 代码:

public class MyServlet extends HttpServlet { 
...
private RandomMessageProducer rmProducer;
private Thread rmpThread; 
...

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Map<Integer, String> msg = new HashMap<Integer, String>();
    Gson gson = new Gson();

    String sDevs   = request.getParameter("devices");  // Option 1
    String sGetMsg = request.getParameter("rec_msg"); // Option 2
    PrintWriter pw = response.getWriter();

    // Request: Send information and run thread
    if (sDevs != null && STATUS==0) {                       

       /* Start a dummy producer */             
       rmProducer = new RandomMessageProducer();
       rmpThread = new Thread( rmProducer )
       rmpThread.start();

       pw.print("{\"1\": \"Action started!\"}");
       STATUS=1;
    }
       //  Request: Receive messages
    else if (sGetMsg != null) {
       List<String> logs = rmProducer.getLogMsg();      
       for (String lmsg : logs) {
            // Check if we can update the current Status
           if (msg.equals("<<<FIN_SUCC>>>") || msg.equals("<<<FIN_ERR>>>")) {
               STATUS=0;                    
           }
           msg.put(0, lmsg);
       }
       String json = gson.toJson(msg);      
       pw.print(json);  
   }
   pw.close();
}
}

这是我简单的消息生产者线程:

public class RandomMessageProducer implements Runnable {

    private Queue<String> msgQueue = new LinkedList<String>();

    @Override
    public void run() {
        Random randomGenerator = new Random();
        for (int idx = 1; idx <= 100; ++idx){
          int randomInt = randomGenerator.nextInt(100);
          msgQueue.add("Generated : " + randomInt);
          try {
            wait(500);          
          } catch (InterruptedException e) {
            msgQueue.add("<<<FIN_ERR>>>");
            e.printStackTrace();
          }
        }
        msgQueue.add("<<<FIN_SUCC>>>");
    }

    public List<String> getLogMsg() {
        List<String> res = new ArrayList<String>();
        while (!msgQueue.isEmpty()) {
            res.add( msgQueue.poll() );
        }
        return res;
    }
}

请求全部执行 1000 毫秒

您可能看到我的推理错误吗?

非常感谢!

4

1 回答 1

2

你在这里有严重的线程安全问题。

首先,wait()当您只想睡几毫秒时,您正在使用它。你应该Thread.sleep()改用。这将解决您的异常,但不会解决线程安全问题。

您有一个由多个线程并行使用的共享链表:随机生成器线程将消息存储在队列中,而 servlet 线程从队列中删除消息。因此,您应该使用并发集合(如 ConcurrentLinkedQueue),或者同步对链表的每次访问。我会使用并发集合。

最后,几个 servlet 线程并行读取和修改rmpThreadrmProducer变量,没有任何同步。rmpThread 变量已写入,但从未读取,因此我将其设为局部变量。为了确保rmProducer其他 servlet 可以看到新写入的内容,您需要同步它的所有访问,方法是使用同步块来写入和读取它,或者通过make 它volatile,或者将它包装到 AtomicReferece 中(这是我的选择将使)。

所以 rmProducer 应该这样声明:

private AtomicReference<RandomMessageProducer> rmProducerRef = new AtomicReference<>();

要修改其值,您应该使用

rmProducerRef.set(rmProducer);

为了得到它,你应该使用

rmProducerRef.get();
于 2013-04-22T11:12:19.050 回答