Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread-safe dynamic pattern for NumberFormat / DecimalFormat

Haven't been able to find appropriate solution in web, therefore I thought to ask if my way of using a java format is correct.

1) In the NumberFormat.java documentation it says that

Number formats are generally not synchronized. It is recommended to create separate format instances for each thread.

We have been using format objects (statically initialized) in a multi-threaded environment with no issues so far. Is it maybe because once the formats are defined, we their state is not changed (ie, no setters are called afterwards)

2) I now need to define a new format which should output either one or two significant digits after comma, depending on some extra logic. The way i did it was to define a new format wrapper and delegate to two distinct DecimalFormat depending on the case in the overwritten #format(double, StringBuffer, FieldPosition) method. Here is the code for that:

private final NumberFormat FORMAT = new DecimalFormat() {
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##");
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat(
                    "0.0#");
    public StringBuffer format(double number, StringBuffer result, java.text.FieldPosition fieldPosition) {
        if ((number >= 10 && Math.ceil(number) == number)) {
            return DECIMAL_FORMAT.format(number, result, fieldPosition);
        } else {
            return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition);
        }
    }
};

Is it the best practice? I have concerns about not actually using the wrapper class (it serves only to comply with the NumberFormat interface and delegates all the work on inner formats). I do not want to call DecimalFormat#applyPattern() as i think this would compromize the volatile concurrency.

Thanks

like image 826
d56 Avatar asked Oct 19 '25 05:10

d56


1 Answers

  1. We have been using format objects (statically initialized) in a multi-threaded environment with no issues so far. Is it maybe because once the formats are defined, we their state is not changed (ie, no setters are called afterwards)

It is impossible to say exactly why you haven't seen any issues, since we don't know exactly how you are using them. Off the top of my head, a few reasons might be:

  • You aren't hitting any of the code paths in DecimalFormat which use the mutable instance variables;
  • You are "coincidentally" applying mutual exclusion, so you're never using the instance in more than one thread at a time;
  • You are using an implementation which actually does synchronize correctly (note the Javadoc says "generally not synchronized", not "never synchronized");
  • You actually are having issues, but you're just not monitoring them adequately;
  • etc.

The thing about synchronization issues, as I saw somebody else comment yesterday, is that you aren't guaranteed to see an issue if you don't synchronize; it's just that you're not guaranteed not to see them either.

The point is that if you're not applying synchronization, you are at the whim and mercy of any number of subtle changes that you may just be totally unaware of. Today it works, tomorrow it doesn't; you'll have one almighty job working out why.

  1. Is it the best practice?

There are a couple of problems I can think of here:

  1. By extending the class, you risk falling foul of the fragile base class problem.

    In a nutshell, unless you are actually calling the public StringBuffer format(double, StringBuffer, java.text.FieldPosition ) method on your DecimalFormat instance explicitly, you can't reliably know whether your overridden method is actually the one called: a change to the implementation of the base class (DecimalFormat) could change the logic you are relying upon to call that method.

  2. You have three mutable instances - FORMAT, DECIMAL_FORMAT and DECIMAL_FORMAT_DIGIT - which have all manner of setters to change their behaviour.

    You should propagate all of those setters to all of the instances, in order that they behave consistently, e.g. if you call setPositivePrefix on FORMAT, you should also call the same method on DECIMAL_FORMAT and DECIMAL_FORMAT_DIGIT.

Unless you actually need to pass FORMAT as a parameter to a method, it would be much more robust if you just defined a plain old method which invokes your logic: basically, move the method you are overriding out of the anonymous subclass:

private static StringBuffer formatWithSpecialLogic(double number, StringBuffer result, java.text.FieldPosition fieldPosition) {

Then you have to call that method explicitly if you want to use that special logic.

like image 88
Andy Turner Avatar answered Oct 21 '25 19:10

Andy Turner