简体   繁体   English

NumberFormat / DecimalFormat的线程安全动态模式

[英]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. 尚未能够在Web中找到合适的解决方案,因此我想问一下我使用Java格式的方式是否正确。

1) In the NumberFormat.java documentation it says that 1)在NumberFormat.java文档中说

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. 2)现在,我需要定义一种新格式,该格式应在逗号后输出一个或两个有效数字,具体取决于一些额外的逻辑。 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. 我的方法是定义一个新的格式包装器,并根据覆盖的#format(double,StringBuffer,FieldPosition)方法中的情况将其委托给两个不同的DecimalFormat。 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). 我担心不实际使用包装器类(它仅用于遵守NumberFormat接口并委托内部格式的所有工作)。 I do not want to call DecimalFormat#applyPattern() as i think this would compromize the volatile concurrency. 我不想调用DecimalFormat#applyPattern(),因为我认为这会损害易失性并发性。

Thanks 谢谢

  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; 您不会在DecimalFormat任何使用可变实例变量的代码路径;
  • 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"); 您正在使用实际上确实正确同步的实现(注意Javadoc说“通常不同步”,而不是“从不同步”);
  • 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. 简而言之,除非您实际上在DecimalFormat实例上显式调用了public StringBuffer format(double, StringBuffer, java.text.FieldPosition )方法,否则您无法可靠地知道重写的方法是否实际上是被称为的方法:基类( DecimalFormat )的实现可能会更改您依赖于调用该方法的逻辑。

  2. You have three mutable instances - FORMAT , DECIMAL_FORMAT and DECIMAL_FORMAT_DIGIT - which have all manner of setters to change their behaviour. 您有三个可变实例FORMATDECIMAL_FORMATDECIMAL_FORMAT_DIGIT它们具有各种设置方法,可以更改其行为。

    You should propagate all of those setters to all of the instances, in order that they behave consistently, eg if you call setPositivePrefix on FORMAT , you should also call the same method on DECIMAL_FORMAT and DECIMAL_FORMAT_DIGIT . 应该将所有这些setter传播到所有实例,以使其行为一致,例如,如果在FORMAT上调用setPositivePrefix ,则还应该在DECIMAL_FORMATDECIMAL_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: 除非您实际上需要将FORMAT作为参数传递给方法,否则,如果您仅定义一个调用逻辑的简单旧方法,它将更加健壮:基本上,将要覆盖的方法移出匿名子类:

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. 然后你 ,如果你要使用特殊的逻辑显式调用该方法。

I don't have the reputation to comment, but in regards to point 2, the biggest downside I see is you haven't overridden all of the behavior of the DecimalFormat class, so your resulting instance will behave inconsistently. 我没有发表评论的声誉,但是关于第2点,我看到的最大弊端是您尚未覆盖DecimalFormat类的所有行为,因此您生成的实例的行为将不一致。 For example: 例如:

final StringBuffer buffer = new StringBuffer();
FORMAT.format(0.111d, buffer, new FieldPosition(0));
System.out.println(buffer.toString());

final StringBuffer buffer2 = new StringBuffer();
FORMAT.format(new BigDecimal(0.111d), buffer2, new FieldPosition(0));
System.out.println(buffer2.toString());

yields 产量

0.11
0.111

It would be better to override all of the necessary methods if you're going that route so you get consistent behavior. 如果您要走那条路线,最好重写所有必要的方法,以便获得一致的行为。 Alternatively you could store a Function in the ThreadLocal that encapsulates the logic you want: 或者,您可以在ThreadLocal中存储一个函数,该函数封装所需的逻辑:

private final ThreadLocal<Function<Double, String>> DECIMAL_FORMATTER = new ThreadLocal<Function<Double, String>>() {
  @Override
  protected Function<Double, String> initialValue() {
    final DecimalFormat decimalFormat = new DecimalFormat("0.##");
    final DecimalFormat decimalFormatDigit = new DecimalFormat("0.0#");
    return (number) -> {
      if ((number >= 10 && Math.ceil(number) == number)) {
        return decimalFormat.format(number);
      } else {
        return decimalFormatDigit.format(number);
      }
    };
  }
};

System.out.println(DECIMAL_FORMATTER.get().apply(0.111d));

Since you say you need to have an instance of NumberFormat , I would suggest you extend that class and implement the required methods, rather than extend DecimalFormat which wasn't intended for inheritance: 既然您说您需要一个NumberFormat实例,那么我建议您扩展该类并实现所需的方法,而不要扩展不是用于继承的DecimalFormat

private final NumberFormat FORMAT = new NumberFormat() {
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##");
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat("0.0#");

    @Override
    public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
        if ((number >= 10 && Math.ceil(number) == number)) {    // or number % 1 == 0
            return DECIMAL_FORMAT.format(number, result, fieldPosition);
        } else {
            return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition);
        }
    }

    @Override
    public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
        return format((double)number, result, fieldPosition);
    }

    @Override
    public Number parse(String source, ParsePosition parsePosition) {
        return DECIMAL_FORMAT.parse(source, parsePosition);
    }
};

This reduces the fragile base class problem, because we're using a base class that's meant to be inherited. 这减少了脆弱的基类问题,因为我们使用的是要继承的基类。 But there's still the issue of all the dead configuration methods. 但是仍然存在所有无效配置方法的问题。 Ideally you would override them to either propagate their changes or throw some exception. 理想情况下,您将覆盖它们以传播其更改或引发某些异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM