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


90

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

10
+1 за підняття цього питання. Так багато людей думають, що SimpleDateFormat є безпечним для потоку (я бачу припущення скрізь).
Адам Гент

Для отримання додаткової інформації про підхід ThreadLocal див .: javaspecialists.eu/archive/Issue172.html
miner49r

І чому, побачити це питання: stackoverflow.com/questions/6840803 / ...
Raedwald

@ 3urdoch Ви помилково пропустили ключове слово "static" у Option-2?
M Faisal Hameed

Відповіді:


43
  1. Створення SimpleDateFormat дороге . Не використовуйте це, якщо це робиться рідко.

  2. Добре, якщо ви можете жити з невеликим блокуванням. Використовуйте, якщо formatDate () використовується мало.

  3. Найшвидший варіант, ЯКЩО ви повторно використовуєте потоки ( пул потоків ). Використовує більше пам'яті, ніж 2., і має більші витрати на завантаження.

Для додатків як 2., так і 3. є життєздатними варіантами. Що найкраще для вашого випадку, залежить від вашого випадку використання. Остерігайтеся передчасної оптимізації. Робіть це, лише якщо вважаєте, що це проблема.

Для бібліотек, які використовуються сторонніми розробниками, я б використовував варіант 3.


Якщо ми використовуємо Option-2 і оголошуємо SimpleDateFormatяк змінну екземпляра, тоді ми можемо використовувати, synchronized blockщоб зробити її безпечною для потоків. Але ехолот показує попереджувальний кальмар-AS2885 . Чи є спосіб вирішити проблему сонара?
M Faisal Hameed

24

Інший варіант - Commons Lang FastDateFormat, але ви можете використовувати його лише для форматування дати, а не для синтаксичного аналізу.

На відміну від Joda, він може виконувати функцію заміни форматування. (Оновлення: Починаючи з версії 3.3.3, FastDateFormat може створювати FastDateParser , що є поточною безпечною заміною SimpleDateFormat)


8
Оскільки Commons Lang 3.2, також FastDateFormatмає parse()метод
manuna

19

Якщо ви використовуєте 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+, а не в версії 3.x
Wisteso 07

4

Не використовуйте SimpleDateFormat, замість цього використовуйте DateTimeFormatter joda-time. Це трохи суворіше з боку розбору, і тому це не зовсім падіння заміни для SimpleDateFormat, але joda-час набагато більш одночасний з точки зору безпеки та продуктивності.


3

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

Здається, що несинхронізація SimpleDateFormat була поганим дизайнерським рішенням з боку дизайнерів Java API; Я сумніваюся, що хтось очікує синхронізації формату () та синтаксичного аналізу ().


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


1

Я щойно реалізував це з варіантом 3, але вніс кілька змін у код:

  • ThreadLocal зазвичай повинен бути статичним
  • Здається, чистіше замінює InitialValue (), а не перевіряє, якщо (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);
    }

0

Уявіть, що у вашої програми є одна нитка. Чому б ви тоді синхронізували доступ до змінної SimpleDataFormat?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.