Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在测试中复制SimpleDataFormat非线程安全性_Java_Multithreading_Junit - Fatal编程技术网

Java 在测试中复制SimpleDataFormat非线程安全性

Java 在测试中复制SimpleDataFormat非线程安全性,java,multithreading,junit,Java,Multithreading,Junit,我有一个应用程序,它不时在日志中显示此stacktrace: java.lang.ArrayIndexOutOfBoundsException: 514 at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436) at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:208

我有一个应用程序,它不时在日志中显示此stacktrace:

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();
                }
            }
        }
    }
@之前
公共作废设置(){
消息=新消息();
pool=Executors.newFixedThreadPool(numOfThreads);
}
@试验
public void多线程测试(){

对于(int i=0;i用
while(true)
替换
for
循环,并等待一段时间。

测试线程安全性(和性能)的一般方法是多次尝试。这与单元测试应该是可重复的(意味着每次运行都有相同的结果)的感觉相冲突.原因是在线程安全的情况下,会涉及到很多机会

为了使testcase在异常时失败,调用
toString()
的每个线程都应该捕获异常并在抛出时失败(junit函数)

try{
    //do stuff
catch(RuntimeException exception){
    fail();
}

100次不太可能足够。我建议至少使用10,00个线程,并且使用的线程数超过您拥有的CPU数,以使机器过载。例如,一台有8个CPU的机器上有32个线程


无论运行多长时间,您都无法通过测试确定代码是线程安全的,因为您只能确定您没有看到它。

您的测试有几个问题:

  • 每个线程仅执行测试方法100次,应增加该次数以增加线程交错场景的数量
  • 调用已同步的
    System.out.println
    =>您正在重新同步代码,这可能会消除问题
还请注意,SimpleDataFormat在内部使用了同步的StringBuffer,因此获得并发问题并不是那么容易

你可以:

  • 使用倒计时锁存器同时启动所有线程并增加交错
  • 删除打印语句
  • 让每个作业多次运行测试方法
试试我的测试

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");
        }
    }
}
公共类Test1{
公共静态void main(字符串[]args)引发异常{
最终简化格式f=新简化格式(“yyyy-MM-dd”);
最终日期d1=f.parse(“2001-01-01”);
最终日期d2=f.parse(“2012-12-12”);
对于(int i=0;i<100;i++){
系统输出打印(i+“”);
最终int j=i;
新线程(){
无效测试(字符串s,预期日期)引发异常{
//已同步(Test1.class){
日期d=f.s;
如果(!d.equals(预期)){
System.out.println(d+“!=”+预期值);
系统出口(1);
}
//}
}
公开募捐{
试一试{
如果(j%2==0){
试验(“2001-01-01”,d1);
}否则{
试验(“2012-12-12”,d2);
}
}捕获(例外e){
e、 printStackTrace();
系统出口(1);
}
};
}.start();
System.out.println(“OK”);
}
}
}

与其正确同步以避免可能的错误发生,不如同步以产生错误,如:

创建多个线程:

while(...)
    synchronized(sync) {
        sync.wait();
    }
    toString();
}

然后调用
sync.notifyAll()
。这可能会增加发现问题的机会。

由于我的第一次测试没有重现您的问题,请尝试此测试

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)

无法保证您可以重现线程安全错误。对于,您可能会发现,在一台机器上,该错误从未发生,而在另一台具有不同硬件的机器上,该错误一直都在发生。我可能不会在一个版本的Java上发生,但在升级时,该错误始终存在。在“result.append(time=).append”中(logDateFormat.format(new Date(getExpireTime()));“什么是时间=?
SimpleDateFormat
(可能还有其他)不是线程安全的。-您应该首先明确确保不在多个线程中使用它,无论是否异常。
时间=
应该是
“Time=”
:)要更改我的应用程序中的代码,首先我需要复制这个bug。异常不是一个不必要的影响吗?您的第一点不正确(有一个循环),在System.out.println()上非常好。结果不需要在测试用例中使用。增加迭代次数可能会有所帮助。谢谢,Evgeny,这失败了,但stacktrace与我的不同。你能为我的问题进行测试吗?我得到
java.lang.ClassCastException:sun.util.calendar.Gregorian$Date
,原因如下:(