Синхронизация доступа к SimpleDateFormat - PullRequest
85 голосов
/ 05 ноября 2010

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);
}

Ответы [ 9 ]

42 голосов
/ 05 ноября 2010
  1. Создание SimpleDateFormat стоит дорого .Не используйте это, если это не делается редко.

  2. Хорошо, если вы можете жить с небольшим количеством блокировки.Используйте, если formatDate () используется мало.

  3. Самый быстрый вариант, если вы повторно используете потоки ( пул потоков ).Использует больше памяти, чем 2. и имеет более высокие накладные расходы при запуске.

Для приложений оба варианта - 2. и 3. являются жизнеспособными.Что лучше для вашего случая, зависит от вашего варианта использования.Остерегайтесь преждевременной оптимизации.Делайте это только в том случае, если считаете, что это проблема.

Для библиотек, которые будут использоваться сторонними организациями, я бы использовал вариант 3.

24 голосов
/ 03 марта 2011

Другой вариант - Commons Lang FastDateFormat , но вы можете использовать его только для форматирования даты, а не для анализа.

В отличие от Joda, он может выполнять функцию замены для форматирования. (Обновление: начиная с версии 3.3.2, FastDateFormat может создавать FastDateParser , который является заменителем потока для SimpleDateFormat, безопасным для вставки) *

15 голосов
/ 23 октября 2014

Если вы используете 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);
6 голосов
/ 19 марта 2014

Commons Lang 3.x теперь имеет FastDateParser, а также FastDateFormat.Это потокобезопасно и быстрее, чем SimpleDateFormat.Он также использует те же спецификации формата / шаблона разбора, что и SimpleDateFormat.

4 голосов
/ 08 ноября 2010

Не используйте SimpleDateFormat, вместо этого используйте DateTimeFormatter joda-time.С парсингом это немного строже, поэтому замена SimpleDateFormat не слишком проста, но время joda гораздо более дружественно по отношению к безопасности и производительности.

3 голосов
/ 09 декабря 2013

Я бы сказал, создайте простой класс-оболочку для SimpleDateFormat, который синхронизирует доступ к parse () и format () и может использоваться в качестве замены.Более надежный, чем ваш вариант № 2, менее громоздкий, чем ваш вариант № 3.

Похоже, что несинхронизированный SimpleDateFormat был плохим дизайнерским решением со стороны разработчиков Java API;Я сомневаюсь, что кто-то ожидает, что format () и parse () должны быть синхронизированы.

1 голос
/ 23 февраля 2016

Я только что реализовал это с помощью Варианта 3, но внес несколько изменений в код:

  • ThreadLocal обычно должен быть статическим
  • Кажется, чище переопределить initialValue (), а не проверять if (get () == null)
  • Возможно, вы захотите установить локаль и часовой пояс, если вы действительно не хотите использовать настройки по умолчанию (значения по умолчанию очень склонны к ошибкам в 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);
    }
    
1 голос
/ 22 мая 2014

Другой вариант - хранить экземпляры в поточно-ориентированной очереди:

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, который используется только один раз.

0 голосов
/ 23 февраля 2014

Представьте, что ваше приложение имеет один поток.Зачем тогда синхронизировать доступ к переменной SimpleDataFormat?

...