"Является ли класс java.text.DateFormat потокобезопасным?". Этот вопрос очень любят задавать на собеседовании по вакансии Java-программиста. Ответ на него – нет, не является. Вот только собеседующие редко спрашивают, а как же тогда быть, если все-таки надо его использовать в многопоточном окружении? Меж тем в реальных задачах мало просто знать, что DateFormat не потокобезопасен, было бы неплохо еще и знать способ решения проблемы. Сравнению возможных вариантов решения и посвящен данный пост.
Элементарная логика и гугл родили на свет пять вариантов:
- Вариант решения "в лоб". Если DateFormat не потокобезопасный, давайте синхронизируем доступ. А чтобы не писать каждый раз блокировки вручную, воспользуемся паттерном декоратор. В точности, как это реализовано в Collections.synchronizedList() итп.
- Другой, не менее очевидный вариант, давайте создавать новый экземпляр класса на каждый вызов.
- Воспользуемся ThreadLocal для хранения одного экземпляра DateFormat на поток.
- Воспользуемся классом из Apache Commons Lang FastDateFormat. Пожалуй основной недостаток этого варианта – он не поддерживает парсинг даты. Поддерживается только печать.
- И последний, но отнюдь не маловажный вариант – использовать библиотечку joda-time. К сожалению, эта библиотека поддерживает не все паттерны стандартного DateFormat поэтому, не во всех случаях будет возможно на нее переключиться.
Что ж, пусть тест производительности решит вопрос и расставит наконец точки над i. В данном тесте проверялась скорость выполнения 100000 операций парсинга и форматирования в каждом из 20 потоков работающих одновременно.Тест запускался дважды, первый раз с ключиком JVM -server, второй раз с ключем -client.
Полный исходный код теста можно скачать
здесь. Для запуска, так же, понадобятся последние версии библиотек
Apache Commons Lang и
Joda Time.
Результаты получились следующие:
| Время парсинга (мс) | Время форматирования (мс) |
Synchronized java.text.DateFormat | 12110 / 13751 | 3484 / 2766 |
New instance per call java.text.DateFormat | 4954 / 10234 | 3750 / 7546 |
ThreadLocal java.text.DateFormat | 1703 / 2687 | 766 / 656 |
Apache FastDateFormat | Не поддерживается | 1500 / 2297 |
Joda Time | 1062 / 1828 | 500 / 1047 |
SimpleDateFormat* | 1697 / 2656 | 758 / 652 |
* - это референсное время, в данном случае мы эффективно используем DateFormat в однопоточном режиме. Создав для каждого из 20 потоков собственный экземпляр класса SimpleDateFormat.
Первое время - это время выполнения для серверной JVM, второе время - для клиентской JVM.
В случае серверной версии, с впечатляющим отрывом лидирует Joda Time, обгоняя даже референсную версию от SimpleDateFormat.
В случае форматирования при клиентской версии JVM в отрыв уходит версия с ThreadLocal.
Может быть, читатель знает какие-то еще варианты решения этой задачи, буду рад их услышать и сравнить с имеющимися.