我有一个应用程序,它不时在日志中有这个堆栈跟踪:
java.lang.ArrayIndexOutOfBoundsException: 514
at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2081)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:1996)
at java.util.Calendar.complete(Calendar.java:1312)
at java.util.Calendar.get(Calendar.java:1093)
at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:917)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:824)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:796)
at java.text.DateFormat.format(DateFormat.java:314)
at me.myself.i.Message.toString(Message.java:203)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuilder.append(StringBuilder.java:116)
我认为这个问题可能存在于某些方面:
public class Message{
private transient DateFormat logDateFormat;
@Override
public String toString() {
final StringBuilder result = new StringBuilder(getClass().getSimpleName());
result.append("Time=").append(logDateFormat.format(new Date(getExpireTime())));
return result.toString();
}
}
我认为多个线程同时调用toString(),但是我在本地机器上复制它时遇到了麻烦:
@Before
public void setUp() {
message = new Message();
pool = Executors.newFixedThreadPool(numOfThreads);
}
@Test
public void multiThreadTest() {
for (int i=0; i<numOfThreads; i++) {
TestJob j = new TestJob(message);
pool.submit(j);
}
pool.shutdown();
while(!pool.isTerminated()){
}
}
class TestJob implements Runnable{
private Message message;
private int n=100;
public TestJob(Message message) {
this.message= message;
}
public void run() {
for (int i=0; i<n; i++) {
try{
System.out.println(message.toString());
} catch(Exception e){
e.printStackTrace();
}
}
}
}
如何编写正确的junit测试来重现此问题?
答案 0 :(得分:8)
由于我的第一次测试没有重现您的问题,请试试这个
final SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
ExecutorService ex = Executors.newFixedThreadPool(1000);
for (;;) {
ex.execute(new Runnable() {
public void run() {
try {
f.format(new Date(new Random().nextLong()));
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
};
});
}
需要时间,但最后我得到了
java.lang.ArrayIndexOutOfBoundsException: 3144942
at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:454)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2333)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2248)
at java.util.Calendar.complete(Calendar.java:1560)
at java.util.Calendar.get(Calendar.java:1162)
at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1093)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:978)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:948)
at java.text.DateFormat.format(DateFormat.java:336)
at Test1$1.run(Test1.java:17)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
答案 1 :(得分:3)
您的测试有几个问题:
System.out.println
,即同步=&gt;您正在重新同步可能会删除问题的代码另请注意,SimpleDateFormat在内部使用同步的StringBuffer,因此获得并发问题并不容易。
你可以:
答案 2 :(得分:2)
测试线程安全(和性能)的一般方法是尝试多次。这与单元测试应该是可重现的意义相冲突(意味着每次运行具有相同的结果)。这样做的原因是,如果线程安全很多,则涉及机会。
为了使测试用例在异常时失败,每个调用toString()
的线程都应该捕获异常并且如果抛出则失败(一个junit函数)。
try{
//do stuff
catch(RuntimeException exception){
fail();
}
答案 3 :(得分:2)
100次是不太可能的。我建议使用至少10,00并使用比cpu更多的线程来使机器超载。例如机器上有32个线程,有8个cpus。
无论你运行多久都无法通过测试确定代码是线程安全的,因为你只能确定你没有看到它。
答案 4 :(得分:2)
试试我的测试,
public class Test1 {
public static void main(String[] args) throws Exception {
final SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
final Date d1 = f.parse("2001-01-01");
final Date d2 = f.parse("2012-12-12");
for (int i = 0; i < 100; i++) {
System.out.print(i + " ");
final int j = i;
new Thread() {
void test(String s, Date expected) throws ParseException {
//synchronized (Test1.class) {
Date d = f.parse(s);
if (!d.equals(expected)) {
System.out.println(d + " != " + expected);
System.exit(1);
}
//}
}
public void run() {
try {
if (j % 2 == 0) {
test("2001-01-01", d1);
} else {
test("2012-12-12", d2);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
};
}.start();
System.out.println("OK");
}
}
}
答案 5 :(得分:1)
将for
循环替换为while(true)
并等待一段时间。
答案 6 :(得分:1)
可能会同步生成错误,而不是正确同步可能发生的错误而不是,例如:
创建多个线程:
while(...)
synchronized(sync) {
sync.wait();
}
toString();
}
然后致电sync.notifyAll()
。 可能会增加查看问题的机会。