|
Quick Lists
|
|
Bug ID:
|
4101500
|
|
Votes
|
0
|
|
Synopsis
|
java.text.NumberFormat is not thread safe
|
|
Category
|
java:classes_text
|
|
Reported Against
|
1.2beta2
|
|
Release Fixed
|
1.1.6
|
|
State
|
11-Closed,
Verified,
bug
|
|
Priority:
|
3-Medium
|
|
Related Bugs
|
4411640
,
4449148
|
|
Submit Date
|
29-DEC-1997
|
|
Description
|
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();
}
}
}
================================================================
xxxxx@xxxxx (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)
======================================================================
|
|
Work Around
|
N/A
|
|
Evaluation
|
The Format subclasses are not designed to be thread safe. The problem is that the thread semantics are not clear in the documentation.
xxxxx@xxxxx 1998-01-06
But it's very easy to make NumberFormat thread-safe in this case, so we went ahead and fixed it anyway.
xxxxx@xxxxx 1998-01-12
|
|
Comments
|
Submitted On 09-FEB-2000
bdzak
Using JDK 1.2, it seems the same problem exists for parse(). With a slightly
tweaked
version of the above test, we were able to create unexpected
NumberFormatExceptions
with even a small number of threads:
import java.text.NumberFormat;
public class NumberFormatTest {
static final NumberFormat sForm = NumberFormat.getNumberInstance();
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.parse("10.00");
}
catch (Exception exc) {
System.out.println(exc);
}
}
}
};
break;
case 1:
r = new Runnable() {
public void run() {
while(true) {
try {
sForm.parse("1.00");
}
catch (Exception exc) {
System.out.println(exc);
}
}
}
};
break;
case 2:
r = new Runnable() {
public void run() {
while(true) {
try {
sForm.parse("0.00");
}
catch (Exception exc) {
System.out.println(exc);
}
}
}
};
break;
}
System.out.println("starting thread number " + i);
new Thread(r).start();
}
}
}
Couldn't the problem be fixed by changing the code in DecimalFormat.parse():
if (!subparse(text, parsePosition, digitList, false, status))
return null;
double doubleResult = 0.0;
long longResult = 0;
boolean gotDouble = true;
// Finally, have DigitList parse the digits into a value.
if (status[STATUS_INFINITE])
{
doubleResult = Double.POSITIVE_INFINITY;
}
else if (digitList.fitsIntoLong(status[STATUS_POSITIVE],
isParseIntegerOnly()))
{
gotDouble = false;
longResult = digitList.getLong();
}
else doubleResult = digitList.getDouble();
to be:
double doubleResult = 0.0;
long longResult = 0;
boolean gotDouble = true;
synchronized(digitList) {
if (!subparse(text, parsePosition, digitList, false, status))
return null;
// Finally, have DigitList parse the digits into a value.
if (status[STATUS_INFINITE])
{
doubleResult = Double.POSITIVE_INFINITY;
}
else if (digitList.fitsIntoLong(status[STATUS_POSITIVE],
isParseIntegerOnly()))
{
gotDouble = false;
longResult = digitList.getLong();
}
else doubleResult = digitList.getDouble();
}
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|
|
|
 |