«Java DateFormat не является streamобезопасным», к чему это приводит?

Все предупреждают, что Java DateFormat не является streamобезопасным, и я понимаю концепцию теоретически.

Но я не могу представить, какие актуальные проблемы мы можем решить из-за этого. Скажем, у меня есть поле DateFormat в classе, и то же самое используется в разных методах в classе (даты форматирования) в многопоточной среде.

Будет ли это причиной:

  • любое исключение, такое как исключение формата
  • несоответствие данных
  • любой другой вопрос?

Также, пожалуйста, объясните, почему.

Попробуем.

Вот программа, в которой несколько streamов используют общий SimpleDateFormat .

Программа :

 public static void main(String[] args) throws Exception { final DateFormat format = new SimpleDateFormat("yyyyMMdd"); Callable task = new Callable(){ public Date call() throws Exception { return format.parse("20101022"); } }; //pool with 5 threads ExecutorService exec = Executors.newFixedThreadPool(5); List> results = new ArrayList>(); //perform 10 date conversions for(int i = 0 ; i < 10 ; i++){ results.add(exec.submit(task)); } exec.shutdown(); //look at the results for(Future result : results){ System.out.println(result.get()); } } 

Запустите это несколько раз, и вы увидите:

Исключения :

Вот несколько примеров:

1.

 Caused by: java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Long.parseLong(Long.java:431) at java.lang.Long.parseLong(Long.java:468) at java.text.DigitList.getLong(DigitList.java:177) at java.text.DecimalFormat.parse(DecimalFormat.java:1298) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

2.

 Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

3.

 Caused by: java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312) 

Неверные результаты :

 Sat Oct 22 00:00:00 BST 2011 Thu Jan 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Thu Oct 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 

Правильные результаты :

 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 

Другим подходом к безопасному использованию DateFormats в многопоточной среде является использование переменной ThreadLocal для хранения объекта DateFormat , что означает, что каждый stream будет иметь свою собственную копию и не должен ждать, пока другие streamи не выпустят его. Вот как это сделать:

 public class DateFormatTest { private static final ThreadLocal df = new ThreadLocal(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public Date convert(String source) throws ParseException{ Date d = df.get().parse(source); return d; } } 

Вот хороший пост с более подробной информацией.

Я ожидаю, что повреждение данных – например, если вы одновременно разобрали две даты, вы могли бы вызвать один вызов, загрязненный данными из другого.

Легко представить, как это может произойти: парсинг часто предполагает сохранение определенного состояния в отношении того, что вы читали до сих пор. Если два streamа будут топтаться в одном и том же состоянии, у вас появятся проблемы. Например, DateFormat предоставляет поле calendar типа « Calendar и, глядя на код SimpleDateFormat , некоторые методы вызывают DateFormat calendar.set(...) а другие вызывают calendar.get(...) . Это явно не является streamобезопасным.

Я не заглядывал в детали о том, почему DateFormat не является streamобезопасным, но для меня достаточно знать, что он небезопасен без синхронизации – точные манеры небезопасности могут даже измениться между релизами.

Лично я использовал бы парсеры из Joda Time вместо того, чтобы они были streamобезопасными – и Joda Time – это гораздо лучший API с датой и временем, чтобы начать с 🙂

Если вы используете Java 8, вы можете использовать DateTimeFormatter .

Форматирование, созданное из шаблона, может использоваться столько раз, сколько необходимо, оно является неизменным и является streamобезопасным.

Код:

 LocalDate date = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String text = date.format(formatter); System.out.println(text); 

Вывод:

 2017-04-17 

Грубо говоря, вы не должны определять DateFormat как переменную экземпляра объекта, к которому обращаются многие streamи, или static .

Форматы даты не синхронизируются. Рекомендуется создавать отдельные экземпляры формата для каждого streamа.

Итак, если ваш Foo.handleBar(..) доступен несколькими streamами, вместо:

 public class Foo { private DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); public void handleBar(Bar bar) { bar.setFormattedDate(df.format(bar.getStringDate()); } } 

вы должны использовать:

 public class Foo { public void handleBar(Bar bar) { DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); bar.setFormattedDate(df.format(bar.getStringDate()); } } 

Кроме того, во всех случаях не имеют static DateFormat

Как отметил Джон Скит, у вас могут быть как статические, так и общие переменные экземпляра в случае, если вы выполняете внешнюю синхронизацию (т. DateFormat Используете synchronized вокруг вызовов в DateFormat )

Форматы даты не синхронизируются. Рекомендуется создавать отдельные экземпляры формата для каждого streamа. Если несколько streamов обращаются к формату одновременно, его необходимо синхронизировать извне.

Это означает, что у вас есть объект DateFormat, и вы получаете доступ к одному и тому же объекту из двух разных streamов, и вы вызываете метод format на этом объекте, и stream будет входить одним и тем же методом одновременно на одном и том же объекте, чтобы вы могли визуализировать его В результате получается правильный результат

Если вам нужно работать с DateFormat, то как тогда вы должны что-то делать

 public synchronized myFormat(){ // call here actual format method } 
  • Справка

Данные повреждены. Вчера я заметил это в своей многопоточной программе, где у меня был статический объект DateFormat и назвал его format() для значений, считываемых через JDBC. У меня был оператор SQL select, где я читал ту же дату с разными именами ( SELECT date_from, date_from AS date_from1 ... ). Такие заявления использовались в 5 streamах для разных дат в WHERE clasue. Даты выглядели «нормальными», но они отличались по стоимости – в то время как все даты были с того же года, менялись только месяц и день.

Другие ответы показывают, как избежать такой коррупции. Я сделал DateFormat не статическим, теперь он является членом classа, который вызывает SQL-запросы. Я тестировал также статическую версию с синхронизацией. Оба работали хорошо, без каких-либо различий в производительности.

Спецификации Format, NumberFormat, DateFormat, MessageFormat и т. Д. Не были разработаны для обеспечения streamобезопасности. Кроме того, метод parse вызывает метод Calendar.clone() и он влияет на следы календаря, поэтому одновременное parsingки streamов будет изменять клонирование экземпляра календаря.

Более того, это отчеты об ошибках, такие как это и это , с результатами проблемы Thread-Safety DateFormat.

В лучшем ответе собака привела пример использования функции parse и того, к чему это приводит. Ниже приведен код, позволяющий проверить функцию format .

Обратите внимание: если вы измените количество исполнителей (параллельные streamи), вы получите разные результаты. Из моих экспериментов:

  • Оставьте newFixedThreadPool 5, и цикл будет терпеть неудачу каждый раз.
  • Установите значение 1, и цикл всегда будет работать (очевидно, как все задачи выполняются один за другим)
  • Установите значение 2, и цикл имеет только около 6% вероятности работы.

Я предполагаю YMMV в зависимости от вашего процессора.

Функция format не работает, форматируя время из другого streamа. Это связано с тем, что функция внутреннего format использует объект calendar который настроен в начале функции format . Объект calendar является свойством classа SimpleDateFormat . Вздох…

 /** * Test SimpleDateFormat.format (non) thread-safety. * * @throws Exception */ private static void testFormatterSafety() throws Exception { final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56); final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56); String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"}; Callable task1 = new Callable() { @Override public String call() throws Exception { return "0#" + format.format(calendar1.getTime()); } }; Callable task2 = new Callable() { @Override public String call() throws Exception { return "1#" + format.format(calendar2.getTime()); } }; //pool with X threads // note that using more then CPU-threads will not give you a performance boost ExecutorService exec = Executors.newFixedThreadPool(5); List> results = new ArrayList<>(); //perform some date conversions for (int i = 0; i < 1000; i++) { results.add(exec.submit(task1)); results.add(exec.submit(task2)); } exec.shutdown(); //look at the results for (Future result : results) { String answer = result.get(); String[] split = answer.split("#"); Integer calendarNo = Integer.parseInt(split[0]); String formatted = split[1]; if (!expected[calendarNo].equals(formatted)) { System.out.println("formatted: " + formatted); System.out.println("expected: " + expected[calendarNo]); System.out.println("answer: " + answer); throw new Exception("formatted != expected"); /** } else { System.out.println("OK answer: " + answer); /**/ } } System.out.println("OK: Loop finished"); } 

Если существует несколько streamов, управляющих / доступ к одному экземпляру DateFormat, и синхронизация не используется, можно получить скремблированные результаты. Это связано с тем, что несколько неатомных операций могут изменять состояние или просматривать память непоследовательно.

Это мой простой код, который показывает, что DateFormat не является streamобезопасным.

 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; runThread(target1); runThread(target2); runThread(target3); } public static void runThread(String target){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } } 

Поскольку все streamи используют один и тот же объект SimpleDateFormat, он выдает следующее исключение.

 Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) 

Но если мы передаем разные объекты в разные streamи, код работает без ошибок.

 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df; public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target1, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target2, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target3, df); } public static void runThread(String target, DateFormat df){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } } 

Это результаты.

 Thread-0 Thu Sep 28 17:29:30 IST 2000 Thread-2 Sat Sep 28 17:29:30 IST 2002 Thread-1 Fri Sep 28 17:29:30 IST 2001 
  • Как сделать мой ArrayList streamобезопасным? Другой подход к проблеме в Java?
  • Остановка streamа на Java?
  • Могу ли я использовать streamи для выполнения длительных заданий в IIS?
  • Ошибка при использовании Task.Factory
  • Неблокирующий pthread_join
  • Показать Загрузка анимации во время загрузки данных в другой stream
  • Java: Как остановить stream?
  • ExecutorService, как ждать завершения всех задач
  • Как правильно реализовать BackgroundWorker с обновлениями ProgressBar?
  • java.lang.IllegalStateException: не для streamа приложений FX; currentThread = Thread-4
  • Разница между изменчивой и синхронизированной в Java
  • Давайте будем гением компьютера.