我的线程看起来向被JVM回收/停掉了,网页访问的时候数据不更新,后台通讯模块显示处理请求的servlet没有发送数据包。


最近写了一个小demo,用来看看实现一个系统需要做些什么,其中一个模块A出现了一个很奇怪的问题。

A需要和模块B通过UDP/IP通讯,同时使用servlet应答web请求。我写了一个servlet,在servlet里init中初始化了成员变量dataContainer,启动了dataContainer中的内部类成员DataContainer dc。

dc是一个继承了Thread的类,在run方法中不停与模块B通讯,获取数据,存放在dc的成员变量StringBuffer shared中。

servlet从dataContainer中获取shared的值来响应请求。

开始的时候一切正常,但是运行时间长了之后,我发现我的thread被停止/回收了(不确定)。

大致这样,上代码。

Servlet:

   
  package servlets;
  

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import support.DataContainer;

/**
* Servlet implementation class DataKeeper
*/
@WebServlet("/DataKeeper")
public class DataService extends HttpServlet {
private static final long serialVersionUID = 1L;

private final DataContainer dc = new DataContainer();

/**
* @see HttpServlet#HttpServlet()
*/
public DataService() {
super();
// TODO Auto-generated constructor stub
}

/**
* @see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
super.init(config);
dc.start();
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("a");
PrintWriter out = response.getWriter();

out.append(dc.getState());
out.flush();
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}

}

辅助类:

   
  /**
  
*
*/
package support;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
* @author Administrator
*
*/
public class DataContainer {

static final String HEXES = "0123456789ABCDEF";

private volatile StringBuffer shared = new StringBuffer();;

private final DataCrawler dc = new DataCrawler();
//private final Thread t = new Thread(dc);

private static String getHex(byte[] raw) {
if (raw == null) {
return null;
}
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(
HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}

class DataCrawler extends Thread{

private static final int PORT = 5000;
private DatagramSocket dataSocket;
private DatagramPacket dataPacket;
private byte sendDataByte[];
private String sendStr;
@Override
public void run() {
// TODO Auto-generated method stub
try {
// 指定端口号,避免与其他应用程序发生冲突
dataSocket = new DatagramSocket(PORT + 1);
sendDataByte = new byte[1500];
sendStr = "I'm in!";
do {
Thread.sleep(500);

sendDataByte = sendStr.getBytes();
dataPacket = new DatagramPacket(sendDataByte,
sendDataByte.length,
InetAddress.getByName("localhost"), PORT);
dataSocket.send(dataPacket);

dataSocket.receive(dataPacket);

shared.setLength(0);
shared.append(getHex(dataPacket.getData()));
System.out.println("udp thread work");
} while (true);
} catch (SocketException se) {
se.printStackTrace();
} catch (IOException ie) {
ie.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

class StateUpdater implements Runnable{

@Override
public void run() {
// TODO Auto-generated method stub

}

}

public void start(){
dc.start();
}
public void stop(){
dc.interrupt();
}
public String getState(){
return this.shared.length() ==0 ? "null" : this.shared.toString();
}
}

页面:

   
  <%@ page language="java" contentType="text/html; charset=GB18030"
  
pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">

<title>Insert title here</title>
<script type="text/javascript">
function getState() {
setInterval(function() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "/Agent4Browser/data", false);
xmlhttp.send();
var res = xmlhttp.responseText;
//alert(res);
document.getElementById("ha").innerHTML = res;
}, 1000);
}
</script>
</head>
<body onload="getState()">
<p id="ha"></p>
</body>
</html>

我观察了一下任务管理器中的jvm,确实在几次垃圾回收之后(内存占用大幅度下降),网页显示的字符不变了(模块B每次发回来的数据不同),而servlet还在响应,run方法中不打印输出到控制台了。

这是为什么?怎么解决?

==================================

我是分割线

我在getState里添加了打印语句

   
  public String getState(){
  
System.out.println(dc.isAlive());
System.out.println(dc.getState());
System.out.println(dc.isInterrupted());
return this.shared.length() ==0 ? "null" : this.shared.toString();
}

并且在run方法中try块的末尾添加了打印语句

   
  public void run() {
  
// TODO Auto-generated method stub
try {
// 指定端口号,避免与其他应用程序发生冲突
dataSocket = new DatagramSocket(PORT + 1);
sendDataByte = new byte[1500];
sendStr = "I'm in!";
do {
Thread.sleep(500);

sendDataByte = sendStr.getBytes();
dataPacket = new DatagramPacket(sendDataByte,
sendDataByte.length,
InetAddress.getByName("localhost"), PORT);
dataSocket.send(dataPacket);

dataSocket.receive(dataPacket);

shared.setLength(0);
shared.append(getHex(dataPacket.getData()));
System.out.println("udp thread work");
} while (true);
} catch (SocketException se) {
se.printStackTrace();
} catch (IOException ie) {
ie.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception ae) {
ae.printStackTrace();
}
}

然后开启了浏览器访问,过了一段时间,页面的内容不负众望不刷新了!

打印出来的内容表明thread还是运行的,但是运行不到打印了,应该是停在前面某个地方导致StringBuffer没有更新。

后来我查看了一下模块B,表明线程没有发送数据包出去,而不是线程死掉了。目前正在查看到底是哪出了问题。

=========================

我是分隔线

track了一下,发现了一件很奇怪的事情:

现在getState方法为

   
  public String getState(){
  
System.out.println(dc.getState());
return this.shared.length() ==0 ? "null" : this.shared.toString();
}

而run方法中添加了

   
  public void run() {
  
// TODO Auto-generated method stub
try {
// 指定端口号,避免与其他应用程序发生冲突
//dataSocket = new DatagramSocket(5000 + 1);

/*dataPacket = new DatagramPacket(sendStr.getBytes(),
sendStr.getBytes().length,
InetAddress.getByName("localhost"), PORT);*/
long temp;
do {
System.out.println(dc.getState());
System.out.println("work");

正常情况下,getState的时候打印的线程状态是TIMED_WAITING,run中打印的是RUNNABLE。出问题的时候run中不再打印状态,getState里打印的是RUNNABLE。

===============

最后一条分割线

最后我尝试给StringBuffer加锁,去掉sleep语句,问题又发生了。
运行正常时间最长的一次是昨天下班之后,机器没关,挂着第二天来都没事,只要我开始操作PC做别的事,在一段时间后就会发生问题。

多线程 jvm

从来不蛋疼 12 years, 10 months ago

问题解决了,原因是UDP会有可能丢失数据,然后线程就卡在获取数据上了。

雨夜的憔悴 answered 12 years, 10 months ago

Your Answer