Синхронизация по SimpleDateFormat против клонирования

Мы знаем, что классы dateformat не являются потокобезопасными. У меня многопоточный сценарий, в котором необходимо использовать формат даты. Я не могу создать новый экземпляр в новом потоке, поскольку создание SimpledateFormat кажется дорогостоящим (конструктор заканчивает вызов "компиляции", который является дорогостоящим). После некоторых тестов для меня остались только два варианта:

  • Внешняя синхронизация - я действительно не хочу этого делать.
  • Клонирование в каждом потоке - Не знаете, есть ли уловы?

Любые предложения?

Если ребята столкнулись с этим раньше, какое направление вы сделали.

Примечание: a аналогичный вопрос был задан раньше, но он был закрыт, указывая на пакет apache. Я не могу использовать для этого новые библиотеки. И я также прочитал этот аналогичный вопрос на SO

Ответы

Ответ 1

Что делать, если вы создали класс, который форматирует даты с использованием пула фиксированных размеров для предварительно обработанных объектов SimpleDateFormat в циклическом режиме? Учитывая, что непротиворечивая синхронизация является дешевой, это может синхронизироваться на объекте SimpleDateFormat, амортизируя коллизии по всему набору.

Таким образом, может быть 50 форматировщиков, каждая из которых используется в свою очередь - столкновение и, следовательно, конфликт блокировки, произойдет только в том случае, если на самом деле было отформатировано 51 дат одновременно.

EDIT 2011-02-19 (PST)

Я реализовал фиксированный пул, как было предложено выше, код, для которого (включая тест), доступен на моем веб-сайте.

Ниже приведены результаты о четырехъядерном процессоре AMD Phenom II 965 BE, работающем в JVM Java 6 SE:

2011-02-19 15:28:13.039 : Threads=10, Iterations=1,000,000
2011-02-19 15:28:13.039 : Test 1:
2011-02-19 15:28:25.450 :   Sync      : 12,411 ms
2011-02-19 15:28:37.380 :   Create    : 10,862 ms
2011-02-19 15:28:42.673 :   Clone     : 4,221 ms
2011-02-19 15:28:47.842 :   Pool      : 4,097 ms
2011-02-19 15:28:48.915 : Test 2:
2011-02-19 15:29:00.099 :   Sync      : 11,184 ms
2011-02-19 15:29:11.685 :   Create    : 10,536 ms
2011-02-19 15:29:16.930 :   Clone     : 4,184 ms
2011-02-19 15:29:21.970 :   Pool      : 3,969 ms
2011-02-19 15:29:23.038 : Test 3:
2011-02-19 15:29:33.915 :   Sync      : 10,877 ms
2011-02-19 15:29:45.180 :   Create    : 10,195 ms
2011-02-19 15:29:50.320 :   Clone     : 4,067 ms
2011-02-19 15:29:55.403 :   Pool      : 4,013 ms

Примечательно, что клонирование и объединение были очень близки друг к другу. В повторных прогонах клонирование было быстрее, чем объединение, так часто, как это было медленнее. Разумеется, тест был специально разработан для экстремального соперничества.

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

Прежде чем принимать окончательное решение так или иначе, я бы хотел тщательно протестировать на различных JVM, версиях и для множества таких объектов. Старые JVM и устройства на небольших устройствах, таких как карманные компьютеры и телефоны, могут иметь гораздо больше накладных расходов при создании объектов и сборе мусора. И наоборот, у них может быть больше накладных расходов на неоспоримую синхронизацию.

FWIW, из моего обзора кода, казалось, что SimpleDateFormat, скорее всего, будет больше всего работать над клонированием.

EDIT 2011-02-19 (PST)

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

2011-02-20 13:26:58.169 : Threads=1, Iterations=10,000,000
2011-02-20 13:26:58.169 : Test 1:
2011-02-20 13:27:07.193 :   Sync      : 9,024 ms
2011-02-20 13:27:40.320 :   Create    : 32,060 ms
2011-02-20 13:27:53.777 :   Clone     : 12,388 ms
2011-02-20 13:28:02.286 :   Pool      : 7,440 ms
2011-02-20 13:28:03.354 : Test 2:
2011-02-20 13:28:10.777 :   Sync      : 7,423 ms
2011-02-20 13:28:43.774 :   Create    : 31,931 ms
2011-02-20 13:28:57.244 :   Clone     : 12,400 ms
2011-02-20 13:29:05.734 :   Pool      : 7,417 ms
2011-02-20 13:29:06.802 : Test 3:
2011-02-20 13:29:14.233 :   Sync      : 7,431 ms
2011-02-20 13:29:47.117 :   Create    : 31,816 ms
2011-02-20 13:30:00.567 :   Clone     : 12,382 ms
2011-02-20 13:30:09.079 :   Pool      : 7,444 ms

Ответ 2

Так как ThreadLocal в вашем случае невозможно, вы должны использовать пул. Получите или создайте новый экземпляр, используйте его и поместите в пул позже.

Ответ 3

Я использовал ThreadLocal для хранения экземпляра каждого формата даты, который я использую.

Ответ 4

Поскольку подобный вопрос, о котором вы говорили, действительно зависит от вашего варианта использования. Если вы повторно используете свои потоки, то использование значения ThreadLocal будет вашим лучшим выбором. Если вы не используете повторно свои потоки, вам нужно взвесить синхронизацию и создание объектов. Если ваши синхронизированные задачи длинные (в случае, когда вам нужно разобрать много дат одновременно), ваши потоки могут оказаться дольше, чем требуется для создания объекта.

Лично я вообще пошел по пути использования значения ThreadLocal, но это потому, что все мои варианты использования были с повторным использованием потока.

Ответ 5

Мы переключились на использование потокобезопасного FastDateFormat

Ответ 6

Необходимо указать, что опция 2: " Клонирование в каждом потоке - Не знаете, есть ли уловы?" является только жизнеспособным вариантом, потому что SimpleDateFormat и DateFormat реализуют глубокий клон(). Неглубокий клон не будет более потокобезопасным, чем просто использовать тот же экземпляр.

Причина, по которой DateFormat и любые подклассы не являются потокобезопасными, обусловлены тем, что DateFormat использует внутренний экземпляр Календаря, который хранится как переменная-член. Вызовы в формате() или parse() полагаются на поля clear(), set() и get() в экземпляре календаря. Любые одновременные вызовы могут повредить внутреннее состояние календаря.

Это также факт, что классы реализуют глубокий clone(), что делает его относительно медленным.

Ответ 7

У меня будет небольшой кеш SimpleDateFormats, который вы можете заблокировать. Каждый замок будет стоить вам 1-2 микросекунды, но вам может понадобиться посмотреть, сколько времени вам нужно для создания нового объекта.