我已经阅读了关于Servlet Thread handling的精彩文章。
我有以下问题:我想创建一个简单的Servlet,它启动一个新的Thread,它在第一个版本中生成随机消息,或者 - 根据参数 - 发送一个响应,其中包含自上次请求以来生成的所有消息。
我在浏览器站点上使用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毫秒
你可能在推理中看到了我的错误吗?
非常感谢!
答案 0 :(得分:2)
这里有严重的线程安全问题。
首先,当你想要做的只是睡眠几毫秒时,你正在使用wait()
。您应该使用Thread.sleep()
代替。这将解决您的异常,但不会解决线程安全问题。
您有一个并行多个线程使用的共享链接列表:随机生成器线程在队列中存储消息,而servlet线程则从队列中移除消息。因此,您应该使用并发集合(如ConcurrentLinkedQueue),或者同步对链接列表的每次访问。我会使用Concurrent集合。
最后,几个servlet线程并行读取和修改rmpThread
和rmProducer
变量,没有任何同步。 rmpThread变量是写的,但从不读,所以我会把它变成局部变量。要确保其他servlet可以看到新写入的rmProducer
,您需要同步其所有访问,方法是使用synchronized块来写入和读取它,或者使其成为volatile
,或者通过将它包装到AtomicReferece中(这是我要做的选择)。
所以rmProducer应该像这样声明:
private AtomicReference<RandomMessageProducer> rmProducerRef = new AtomicReference<>();
要修改其值,您应该使用
rmProducerRef.set(rmProducer);
为了得到它,你可以使用
rmProducerRef.get();