Статьи

Лучшие практики Java — DateFormat в многопоточной среде

Это первая из серии статей, касающихся предлагаемых практик при работе с языком программирования Java.

Все обсуждаемые темы основаны на сценариях использования, полученных в результате разработки критически важных, высокопроизводительных производственных систем для телекоммуникационной отрасли.

Перед прочтением каждого раздела этой статьи настоятельно рекомендуется ознакомиться с соответствующей документацией по Java API для получения подробной информации и примеров кода.

Все тесты выполняются на Sony Vaio со следующими характеристиками:

  • Система: openSUSE 11.1 (x86_64)
  • Процессор (ЦП): Процессор Intel® Core ™ 2 Duo T6670 с частотой 2,20 ГГц
  • Скорость процессора: 1200,00 МГц
  • Общий объем памяти (ОЗУ): 2,8 ГБ
  • Java: OpenJDK 1.6.0_0 64-битная

Применяется следующая тестовая конфигурация:

  • Параллельный рабочий потоков: 200
  • Тест повторений на одного работника Тема: 1000
  • Всего тестовых прогонов: 100

Использование DateFormat в многопоточной среде

Работать с DateFormat в многопоточной среде может быть сложно. В документации Java API четко говорится:

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

Типичным сценарием случая является преобразование Date в его строковое представление или наоборот, используя предопределенный формат. Создание новых экземпляров DateFormat для каждого преобразования очень неэффективно. Следует помнить, что статические фабричные методы «getDateInstance (..)» также создают новые экземпляры DateFormat при использовании. Большинство разработчиков делают то, что они создают экземпляр DateFormat , используя класс реализации DateFormat (например, SimpleDateFormat ), и присваивают его значение переменной класса. Переменная области видимости класса используется для всех нужд анализа и форматирования даты . Вышеупомянутый подход, хотя и очень эффективный, может вызвать проблемы, когда несколько потоков обращаются к одному и тому же экземпляру переменной класса, из-за отсутствия синхронизации в классе DateFormat . Типичные исключения, возникающие при разборе для создания объекта Date :

  • java.lang.NumberFormatException
  • java.lang.ArrayIndexOutOfBoundsException

Вы также должны испытать искаженное представление даты в строку при форматировании.

Чтобы правильно справиться с вышеупомянутыми проблемами, важно уточнить архитектуру вашей многопоточной среды. Виртуальная машина Java позволяет приложению иметь несколько потоков выполнения, запущенных одновременно. Как правило, в среде с многопоточностью (либо в контейнере внутри JVM, либо в самой JVM) должен выполняться пул потоков . Рабочие потоки должны создаваться и инициализироваться при запуске, использоваться для выполнения ваших программ. Например, веб-контейнер создает пул рабочих потоков для обслуживания всего входящего трафика. Пул потоков является наиболее эффективным способом манипулирования системными ресурсами, главным образом из-за того, что создание и инициализация потоков является задачей, требующей больших ресурсов для виртуальной машины Java. Тем не менее параллелизм приложения может быть достигнут путем простого создания нового потока выполнения для каждого фрагмента кода, который вы хотите выполнять одновременно.

Относительно экземпляров DateFormat, относящихся к классу:

  • Если вы пояснили, что в вашей среде НЕТ пулов потоков, тогда только новые экземпляры потоков одновременно получают доступ к вашему экземпляру DateFormat . В этом случае рекомендуется синхронизировать этот экземпляр DateFormat внешне
  • В случае использования пулов потоков существует ограниченное количество экземпляров потока, которые могут одновременно обращаться к вашему экземпляру DateFormat . Таким образом, рекомендуется создавать отдельные экземпляры DateFormat для каждого потока, используя подход ThreadLocal.

Ниже приведены примеры подходов «getDateInstance (..)», «синхронизация» и ThreadLocal :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package com.javacodegeeks.test;
 
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class ConcurrentDateFormatAccess {
 
 public Date convertStringToDate(String dateString) throws ParseException {
  return SimpleDateFormat.getDateInstance(DateFormat.MEDIUM).parse(dateString);
 }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.javacodegeeks.test;
 
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class ConcurrentDateFormatAccess {
 
 private DateFormat df = new SimpleDateFormat("yyyy MM dd");
 
 public Date convertStringToDate(String dateString) throws ParseException {
  Date result;
  synchronized(df) {
   result = df.parse(dateString);
  }
  return result;
 }
 
}

Что следует отметить здесь:

  • Каждый отдельный поток, выполняющий операцию «convertStringToDate», пытается получить блокировку монитора для объекта DateFormat до получения ссылки на экземпляр переменной класса DateFormat . Если другой поток удерживает блокировку, то текущий поток ожидает, пока блокировка не будет снята. Таким образом, только один поток одновременно обращается к экземпляру DateFormat.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.javacodegeeks.test;
 
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class ConcurrentDateFormatAccess {
 
 private ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat> () {
 
  @Override
  public DateFormat get() {
   return super.get();
  }
 
  @Override
  protected DateFormat initialValue() {
   return new SimpleDateFormat("yyyy MM dd");
  }
 
  @Override
  public void remove() {
   super.remove();
  }
 
  @Override
  public void set(DateFormat value) {
   super.set(value);
  }
 
 };
 
 public Date convertStringToDate(String dateString) throws ParseException {
  return df.get().parse(dateString);
 }
 
}

Что следует отметить здесь:

  • Каждый отдельный поток, выполняющий операцию «convertStringToDate», вызывает операцию «df.get ()», чтобы инициализировать или извлечь уже инициализированную ссылку своего экземпляра DateFormat локальной области видимости.

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

Горизонтальная ось представляет количество тестовых прогонов, а вертикальная ось — среднее количество транзакций в секунду (TPS) для каждого тестового прогона. Таким образом, чем выше значения, тем лучше. Как вы можете видеть, используя пулы потоков и подход ThreadLocal, вы можете добиться превосходной производительности по сравнению с подходами «синхронизации» и «getDateInstance (..)».

Наконец, позвольте мне указать, что использование подхода ThreadLocal без пулов потоков эквивалентно использованию подхода «getDateInstance (..)» из-за того факта, что каждый новый поток должен инициализировать свой локальный экземпляр DateFormat перед его использованием, таким образом, новый DateFormat Экземпляр будет создан при каждом выполнении.

Удачного кодирования!

Джастин

Статьи по Теме :