2013-04-22 19 views
0

我已經閱讀了關於Servlet Thread handling的偉大文章。Java 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; 
    } 
} 

的請求是執行全部1000ms

您是否可能在推理中看到我的錯誤?

非常感謝!

+0

什麼是RandomMessageProducer中的第35行? – Elior 2013-04-22 10:54:06

回答

2

您在這裏有嚴重的線程安全問題。

首先,當您想要做的只是睡眠幾毫秒時,您正在使用wait()。您應該使用Thread.sleep()。這將解決您的異常,但不會解決線程安全問題。

您有一個由多個線程並行使用的共享鏈表:隨機生成器線程在隊列中存儲消息,而servlet線程從隊列中消除消息。因此,您應該使用併發集合(如ConcurrentLinkedQueue),或者將每個訪問同步到鏈接列表。我會使用Concurrent集合。

最後,幾個servlet線程並行讀取和修改rmpThreadrmProducer變量,沒有任何類型的同步。 rmpThread變量被寫入,但從未讀取,所以我將它作爲一個局部變量。爲了確保新寫入的rmProducer對其他servlet可見,您需要同步其所有訪問,可以使用同步塊來寫入和讀取它,也可以通過將其寫入volatile,或者將它包裝到AtomicReferece中(這是我會做的選擇)。

所以rmProducer應該聲明如下:

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

要修改它的價值,你應該使用

rmProducerRef.set(rmProducer); 

而要得到它,你shuld使用

rmProducerRef.get(); 
+0

好吧,我看到我必須關心自己有效的線程處理。感謝您的詳細描述! – 2013-04-23 10:41:52