From: Yoshito Umaoka Date: Thu, 13 Nov 2014 18:50:44 +0000 (+0000) Subject: ICU-11363 Updated SimpleDateFormat clone implementation to create a copy of an array... X-Git-Tag: milestone-59-0-1~1447 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a554fd430a7583fba2c140bd4b79366aa597599a;p=icu ICU-11363 Updated SimpleDateFormat clone implementation to create a copy of an array used for numeric field formatting. X-SVN-Rev: 36729 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/DateNumberFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/DateNumberFormat.java index 03349de1854..7f75bfae654 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/DateNumberFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/DateNumberFormat.java @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2007-2011, International Business Machines +* Copyright (C) 2007-2014, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************* */ @@ -23,6 +23,7 @@ import com.ibm.icu.util.UResourceBundle; /* * NumberFormat implementation dedicated/optimized for DateFormat, * used by SimpleDateFormat implementation. + * This class is not thread-safe. */ public final class DateNumberFormat extends NumberFormat { @@ -33,7 +34,8 @@ public final class DateNumberFormat extends NumberFormat { private char minusSign; private boolean positiveOnly = false; - private transient char[] decimalBuf = new char[20]; // 20 digits is good enough to store Long.MAX_VALUE + private static final int DECIMAL_BUF_SIZE = 20; // 20 digits is good enough to store Long.MAX_VALUE + private transient char[] decimalBuf = new char[DECIMAL_BUF_SIZE]; private static SimpleCache CACHE = new SimpleCache(); @@ -123,7 +125,7 @@ public final class DateNumberFormat extends NumberFormat { } public char[] getDigits() { - return digits; + return digits.clone(); } public StringBuffer format(double number, StringBuffer toAppendTo, @@ -254,7 +256,15 @@ public final class DateNumberFormat extends NumberFormat { setZeroDigit(zeroDigit); } // re-allocate the work buffer - decimalBuf = new char[20]; + decimalBuf = new char[DECIMAL_BUF_SIZE]; + } + + @Override + public Object clone() { + DateNumberFormat dnfmt = (DateNumberFormat)super.clone(); + dnfmt.digits = this.digits.clone(); + dnfmt.decimalBuf = new char[DECIMAL_BUF_SIZE]; + return dnfmt; } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java index bf7f59843cd..cf0b6f44eae 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java @@ -1995,6 +1995,10 @@ public class SimpleDateFormat extends DateFormat { } } + /* + * Initializes transient fields for fast simple numeric formatting + * code. This method should be called whenever number format is updated. + */ private void initLocalZeroPaddingNumberFormat() { if (numberFormat instanceof DecimalFormat) { decDigits = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getDigits(); @@ -2007,14 +2011,15 @@ public class SimpleDateFormat extends DateFormat { } if (useLocalZeroPaddingNumberFormat) { - decimalBuf = new char[10]; // sufficient for int numbers + decimalBuf = new char[DECIMAL_BUF_SIZE]; } } // If true, use local version of zero padding number format private transient boolean useLocalZeroPaddingNumberFormat; - private transient char[] decDigits; - private transient char[] decimalBuf; + private transient char[] decDigits; // read-only - can be shared by multiple instances + private transient char[] decimalBuf; // mutable - one per instance + private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers /* * Lightweight zero padding integer number format function. @@ -3470,6 +3475,11 @@ public class SimpleDateFormat extends DateFormat { public Object clone() { SimpleDateFormat other = (SimpleDateFormat) super.clone(); other.formatData = (DateFormatSymbols) formatData.clone(); + // We must create a new copy of work buffer used by + // the fast numeric field format code. + if (this.decimalBuf != null) { + other.decimalBuf = new char[DECIMAL_BUF_SIZE]; + } return other; } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java index e5f15f34aac..eacd70519d1 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java @@ -1427,5 +1427,57 @@ public class DateFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk { } } - + + // Test case for numeric field format threading problem + public void TestT11363() { + + class TestThread extends Thread { + SimpleDateFormat fmt; + Date d; + + TestThread(SimpleDateFormat fmt, Date d) { + this.fmt = fmt; + this.d = d; + } + + public void run() { + String s0 = fmt.format(d); + for (int i = 0; i < 1000; i++) { + String s = fmt.format(d); + if (!s0.equals(s)) { + errln("Result: " + s + ", Expected: " + s0); + } + } + } + } + + SimpleDateFormat fmt0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + Thread[] threads = new Thread[10]; + + GregorianCalendar cal = new GregorianCalendar(2014, Calendar.NOVEMBER, 5, 12, 34, 56); + cal.set(Calendar.MILLISECOND, 777); + + // calls format() once on the base object to trigger + // lazy initialization stuffs. + fmt0.format(cal.getTime()); + + for (int i = 0; i < threads.length; i++) { + // Add 1 to all fields to use different numbers in each thread + cal.add(Calendar.YEAR, 1); + cal.add(Calendar.MONTH, 1); + cal.add(Calendar.DAY_OF_MONTH, 1); + cal.add(Calendar.HOUR_OF_DAY, 1); + cal.add(Calendar.MINUTE, 1); + cal.add(Calendar.SECOND, 1); + cal.add(Calendar.MILLISECOND, 1); + Date d = cal.getTime(); + SimpleDateFormat fmt = (SimpleDateFormat)fmt0.clone(); + threads[i] = new TestThread(fmt, d); + } + + for (Thread t : threads) { + t.start(); + } + } }