Синхронизация доступа к SimpleDateFormat

91

В 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);
}
3эрдок
источник
10
+1 за поднятие этого вопроса. Многие люди думают, что SimpleDateFormat является потокобезопасным (я везде вижу предположения).
Адам Гент
Для получения дополнительной информации о подходе ThreadLocal см .: javaspecialists.eu/archive/Issue172.html
miner49r
И почему, см. Этот вопрос: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch Вы по ошибке пропустили ключевое слово static в Варианте 2?
M Faisal Hameed

Ответы:

43
  1. Создание SimpleDateFormat стоит дорого . Не используйте это, если это не делается редко.

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

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

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

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

Петр Кнего
источник
Если мы используем Option-2 и объявляем SimpleDateFormatкак переменную экземпляра, мы можем использовать ее, synchronized blockчтобы сделать ее потокобезопасной. Но гидролокатор показывает предупреждение squid-AS2885 . Есть ли способ решить проблему с сонаром?
M Faisal Hameed
24

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

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

Адам Гент
источник
8
Начиная с Commons Lang 3.2, также FastDateFormatесть parse()метод
мануна
20

Если вы используете 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

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

Час
источник
Он доступен только в
версии 3.2+, но
4

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

Джед Уэсли-Смит
источник
3

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

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

Энди
источник
1

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

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

SamJ
источник
1

Я только что реализовал это с Вариантом 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);
    }
    
mwk
источник
0

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

Вихрь
источник