В javadoc для SimpleDateFormat указано, что SimpleDateFormat не синхронизируется.
«Форматы даты не синхронизируются. Рекомендуется создавать отдельные экземпляры формата для каждого потока. Если несколько потоков обращаются к формату одновременно, он должен быть синхронизирован извне».
Но как лучше всего использовать экземпляр SimpleDateFormat в многопоточной среде? Вот несколько вариантов, о которых я подумал, я использовал варианты 1 и 2 в прошлом, но мне любопытно узнать, есть ли какие-нибудь лучшие альтернативы или какой из этих вариантов будет предлагать лучшую производительность и параллелизм.
Вариант 1. При необходимости создайте локальные экземпляры
public String formatDate(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
}
Вариант 2. Создайте экземпляр SimpleDateFormat в качестве переменной класса, но синхронизируйте доступ к нему.
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
synchronized(sdf) {
return sdf.format(d);
}
}
Вариант 3. Создайте ThreadLocal для хранения разных экземпляров SimpleDateFormat для каждого потока.
private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
SimpleDateFormat sdf = tl.get();
if(sdf == null) {
sdf = new SimpleDateFormat("yyyy-MM-hh");
tl.set(sdf);
}
return sdf.format(d);
}
Ответы:
Создание SimpleDateFormat стоит дорого . Не используйте это, если это не делается редко.
Хорошо, если вы можете жить с небольшим блокированием. Используйте, если formatDate () мало используется.
Самый быстрый вариант, ЕСЛИ вы повторно используете потоки ( пул потоков ). Использует больше памяти, чем 2. и имеет более высокие затраты на запуск.
Для приложений приемлемы варианты 2 и 3. Что лучше всего для вашего случая, зависит от вашего варианта использования. Остерегайтесь преждевременной оптимизации. Делайте это только в том случае, если считаете, что это проблема.
Для библиотек, которые будут использоваться сторонними организациями, я бы использовал вариант 3.
источник
SimpleDateFormat
как переменную экземпляра, мы можем использовать ее,synchronized block
чтобы сделать ее потокобезопасной. Но гидролокатор показывает предупреждение squid-AS2885 . Есть ли способ решить проблему с сонаром?Другой вариант - Commons Lang FastDateFormat, но вы можете использовать его только для форматирования даты, но не для синтаксического анализа.
В отличие от Joda, он может выполнять функцию прямой замены форматирования. (Обновление: начиная с версии 3.3.2 FastDateFormat может создавать FastDateParser , который является поточно- поточной заменой SimpleDateFormat)
источник
FastDateFormat
естьparse()
методЕсли вы используете Java 8, вы можете использовать
java.time.format.DateTimeFormatter
:например:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String str = new java.util.Date().toInstant() .atZone(ZoneId.systemDefault()) .format(formatter);
источник
Commons Lang 3.x теперь имеет FastDateParser, а также FastDateFormat. Это потокобезопасный и быстрее, чем SimpleDateFormat. Он также использует те же спецификации формата / шаблона анализа, что и SimpleDateFormat.
источник
Не используйте SimpleDateFormat, вместо этого используйте DateTimeFormatter joda-time. Он немного строже в части синтаксического анализа и поэтому не совсем подходит для замены SimpleDateFormat, но joda-time намного удобнее с точки зрения безопасности и производительности.
источник
Я бы сказал, создайте простой класс-оболочку для SimpleDateFormat, который синхронизирует доступ к parse () и format () и может использоваться в качестве замены. Более надежный, чем ваш вариант №2, менее громоздкий, чем ваш вариант №3.
Похоже, что создание несинхронизированного SimpleDateFormat было плохим дизайнерским решением со стороны разработчиков Java API; Я сомневаюсь, что кто-то ожидает, что нужно будет синхронизировать format () и parse ().
источник
Другой вариант - сохранить экземпляры в потокобезопасной очереди:
import java.util.concurrent.ArrayBlockingQueue; private static final int DATE_FORMAT_QUEUE_LEN = 4; private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN); // thread-safe date time formatting public String format(Date date) { SimpleDateFormat fmt = dateFormatQueue.poll(); if (fmt == null) { fmt = new SimpleDateFormat(DATE_PATTERN); } String text = fmt.format(date); dateFormatQueue.offer(fmt); return text; } public Date parse(String text) throws ParseException { SimpleDateFormat fmt = dateFormatQueue.poll(); if (fmt == null) { fmt = new SimpleDateFormat(DATE_PATTERN); } Date date = null; try { date = fmt.parse(text); } finally { dateFormatQueue.offer(fmt); } return date; }
Размер dateFormatQueue должен быть примерно равен предполагаемому количеству потоков, которые могут одновременно вызывать эту функцию. В худшем случае, когда больше потоков, чем это число, фактически используют все экземпляры одновременно, будут созданы несколько экземпляров SimpleDateFormat, которые не могут быть возвращены в dateFormatQueue, потому что он заполнен. Это не приведет к ошибке, это просто повлечет за собой штраф в виде создания некоторого SimpleDateFormat, который используется только один раз.
источник
Я только что реализовал это с Вариантом 3, но внес несколько изменений в код:
Вы можете установить языковой стандарт и часовой пояс, если вам действительно не нужны настройки по умолчанию (значения по умолчанию очень подвержены ошибкам с Java)
private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); return sdf; } }; public String formatDate(Date d) { return tl.get().format(d); }
источник
Представьте, что ваше приложение имеет один поток. Зачем тогда синхронизировать доступ к переменной SimpleDataFormat?
источник