3

私は時々このスタックトレースをログに記録するアプリケーションを持っています:

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 テストを作成するにはどうすればよいですか?

4

7 に答える 7

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)
于 2012-12-21T11:12:22.760 に答える
3

テストにはいくつかの問題があります。

  • 各スレッドは、テストされたメソッドを 100 回しか実行しません。これを増やして、スレッド インターリーブ シナリオの数を増やす必要があります。
  • System.out.printlnあなたは同期されているものを呼び出します=>問題を解決する可能性のあるコードを再同期しています

また、SimpleDateFormat は同期化された StringBuffer を内部で使用するため、同時発生の問題が発生するのはそれほど簡単ではないことに注意してください。

あなたは出来る:

  • CountDownLatch を使用してすべてのスレッドを同時に開始し、インターリーブを増やします
  • 印刷ステートメントを削除します
  • 各ジョブにテスト済みのメソッドを何度も実行させる
于 2012-12-21T10:46:10.153 に答える
2

スレッドセーフ(およびパフォーマンス)をテストする一般的な方法は、何度も試すことです。これは、単体テストが再現可能である必要があるという意味と矛盾します(つまり、各実行で同じ結果が得られます)。この理由は、スレッドセーフが多い場合にチャンスが関係するためです。

テストケースが例外で失敗するためには、呼び出す各スレッドがtoString()例外をキャッチし、例外がスローされた場合は失敗する必要があります(junit関数)。

try{
    //do stuff
catch(RuntimeException exception){
    fail();
}
于 2012-12-21T10:39:12.810 に答える
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");
        }
    }
}
于 2012-12-21T10:47:04.123 に答える
2

100回では十分ではありません。少なくとも 10,00 スレッドを使用し、CPU を使用してマシンを過負荷にすることをお勧めします。たとえば、8 CPU のマシンで 32 スレッド。

どれだけ長く実行しても、テストによってコードがスレッドセーフであるかどうかを判断することはできません。

于 2012-12-21T10:42:07.330 に答える
1

forループをに置き換えてwhile(true)、しばらく待ちます。

于 2012-12-21T10:39:04.790 に答える
1

バグが発生しないように適切に同期する代わりに、次のように同期してバグを生成することがあります。

複数のスレッドを作成します。

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

を呼び出しますsync.notifyAll()。これにより、問題が発生する可能性が高くなる可能性があります。

于 2012-12-21T11:00:29.310 に答える