2010-11-05 8 views
81

В javadoc для SimpleDateFormat указано, что 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); 
} 
+10

+1 для поднятия этого вопроса. Так много людей думают, что SimpleDateFormat является потокобезопасным (я вижу допущения везде). –

+0

Для получения дополнительной информации о подходе ThreadLocal см .: http://www.javaspecialists.eu/archive/Issue172.html – miner49r

+0

И для чего, см. Этот вопрос: http://stackoverflow.com/questions/6840803/simpledateformat- thread-safety – Raedwald

ответ

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

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

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

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

Для библиотек, которые будут использоваться на 3-й партии я хотел бы использовать вариант 3.

+0

Если мы используем Option-2 и объявим 'SimpleDateFormat' как переменную экземпляра, мы можем использовать' synchronized block', чтобы сделать его потокобезопасным. Но сонар показывает предупреждение [squid-AS2885] (https://sonarqube.com/coding_rules#rule_key=squid%3AS2885). Есть ли способ решить проблему сонара? –

4

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

23

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

В отличие от Joda, он может функционировать как замена для форматирования. (Update: С v3.3.2, FastDateFormat может производить FastDateParser, которое капля в потокобезопасного для замены SimpleDateFormat)

+8

Так как Commons Lang 3.2, 'FastDateFormat' имеет' parse() 'метод также – manuna

3

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

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

0

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

6

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

+0

Он доступен только в версии 3.2+, а не 3.x – Wisteso

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

11

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

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

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