JDK-4101500 : java.text.NumberFormat is not thread safe
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.text
  • Affected Version: 1.2.0
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_2.5.1
  • CPU: sparc
  • Submitted: 1997-12-29
  • Updated: 1998-08-14
  • Resolved: 1998-08-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other
1.1.6 1.1.6Fixed 1.2.0Fixed
Related Reports
Relates :  
Relates :  
Description

Name: ccC48265			Date: 12/29/97

IN BRIEF:
The JDK's java.text.NumberFormat class and its subclasses are not
thread-safe. The act of formatting a number actually stores some
transient state in the formatting object itself. If simultaneous threads
attempt to use the same formatting object to format a number, the
object can get confused and throw all kinds of runtime exceptions,
mostly of the StringIndexOutOfBounds variety.

We noticed this in JDK 1.1.4 and I confirmed with a diff of the source
files between 1.2 and 1.1.4 (the only thing changed are comments).

BACKGROUND AND TEST CASE:
The bug is self-evident from examining the code in java.text.DecimalFormat's
format() method.

    // Overrides
    public StringBuffer format(double number, StringBuffer result,
                               FieldPosition fieldPosition)
    {
        // Initialize
        fieldPosition.beginIndex = fieldPosition.endIndex = 0;

        if (Double.isNaN(number)) {
            result.append(symbols.getNaN());
        } else {
            boolean isNegative = (number < 0);
            if (!isNegative)
                result.append(positivePrefix);
            else {
                result.append(negativePrefix);
                number = -number;
            }
            if (Double.isInfinite(number)) {
                result.append(symbols.getInfinity());
            } else {
                if (multiplier != 1) number *= multiplier;
                digitList.set(number, getMaximumFractionDigits());
                appendNativeDigits(result, fieldPosition);
            }
            if (!isNegative)
                result.append(positiveSuffix);
            else result.append(negativeSuffix);
        }
        return result;
    }

In the above, digitList is a member variable of the DecimalFormat object.
This code is manipulating the state of the member variable in
digitList.set(...); this state is later consulted in the
appendNativeDigits() method.  Obviously, using member variable state to
communicate between these methods is not thread-safe.

We have written you this small test program, which will occasionally
barf with OutOfArrayExceptions. But it doesn't do it very often, as is
the case with threading problems sometimes. ;) So you might need to just
look at the "logic problem" we've pointed out above.

e.g. % java NumberFormatTest 1000

import java.text.NumberFormat;

public class NumberFormatTest {

  static final NumberFormat sForm = NumberFormat.getCurrencyInstance();

  public static void main(String [] pArgs) {
    sForm.setMinimumFractionDigits(2);
    sForm.setMaximumFractionDigits(2);

    // do formatting in many threads:
    for (int i = 0; i < Integer.parseInt(pArgs[0]); i++) {
      Runnable r = null;

      switch (i % 3) {
      case 0:
        r = new Runnable() {
          public void run() {
              while(true) {
                try {
                  sForm.format(10.00);
                }
                catch (Exception exc) {
                  System.out.println(exc);
                }
              }
          }
        };
        break;

      case 1:
        r = new Runnable() {
          public void run() {
              while(true) {
                try {
                  sForm.format(1.00);
                }
                catch (Exception exc) {
                  System.out.println(exc);
                }
              }
          }
        };
        break;

      case 2:
        r = new Runnable() {
          public void run() {
              while(true) {
                try {
                  sForm.format(0.00);
                }
                catch (Exception exc) {
                  System.out.println(exc);
                }
              }
          }
        };
        break;

      }

      System.out.println("starting thread number " + i);
      new Thread(r).start();
    }
  }

}
================================================================
###@###.### (Dec 29, 1997):
Using JDK1.2beta2, I was UNable to reproduce this bug:
I tried to run the test case many times, but no exceptions
were thrown...

However, with this application (as with many threading
applications) there is no guarantee that an exception will
be thrown.  More importantly, though, the submitter brings
up compelling arguments why something is fundamentally wrong
with the code for java.text.DecimalFormat's  format(double,
StringBuffer, FieldPosition)  method.  This would justify
a review of the code.
(Review ID: 22011)
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.2beta3 generic FIXED IN: 1.1.6 1.2beta3 INTEGRATED IN: 1.1.6 1.2beta3 VERIFIED IN: 1.1.6
14-06-2004

EVALUATION The Format subclasses are not designed to be thread safe. The problem is that the thread semantics are not clear in the documentation. brian.beck@Eng 1998-01-06 But it's very easy to make NumberFormat thread-safe in this case, so we went ahead and fixed it anyway. ###@###.### 1998-01-12
06-01-1998