Путаница в отношении безопасности потоков - пример SimpleDateFormat

У меня вопрос о безопасности потоков. Из того, что мне сказали, SimpleDateFormat не является потокобезопасным. Мне было интересно, какие последствия он будет иметь, если я использую его следующим образом в моем контроллере spring:

private final static SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd yyyy", Locale.US);

Позже в моих функциях контроллера я использую его следующим образом:

  try {
        changedate = changedate.substring(0, 15);                                                
        calcDate = dateFormat.parse(changedate);
    } catch (ParseException e2) {
        logger.error("Date Parsing Problem", e2); 
    }

calcDate затем добавляется к моему объекту модели и возвращается ModelAndView.

Итак, какие проблемы я увижу, используя его таким образом? Удалил бы статическое ключевое слово исправить любые проблемы, потому что тогда каждый поток будет использовать свой собственный экземпляр dateFormat? Любая ясность в этом подтеке в отношении безопасности потоков будет высоко оценена.

Спасибо

Ответ 1

SimpleDateFormat.parse() используется переменная экземпляра с именем calendar для создания даты из строки. Если два потока пытаются разобрать в одно и то же время, переменная calendar будет сбита, и вы получите неправильные результаты.

Приведение переменной не статичной не обязательно поможет, так как два потока все равно могут использовать один и тот же контроллер. Лучшим решением является либо создание нового объекта DateFormat каждый раз при анализе даты, либо использование локального хранилища потоков. Еще лучше используйте JodaTime, в котором есть поточно-безопасные парсеры.

Ответ 2

Итак, какие проблемы я увижу, используя его таким образом?

Разработчики SimpleDateFormat приняли очень странное решение - во время работы parse() они хранят частично обработанную дату в поле SimpleDateFormat. Очевидно, это означает, что вы не можете вызывать parse() из нескольких потоков одновременно.

Было бы просто удалить статическое ключевое слово исправить любые проблемы, потому что тогда каждый поток будет использовать свой собственный экземпляр dateFormat?

Удаление static вам не поможет, поскольку контроллеры Spring по умолчанию имеют одинарный размер, поэтому Spring использует один экземпляр вашего контроллера для обслуживания всех запросов.

Ответ 3

Не уверен, какие проблемы вы увидите, если бы сделали это так. Но Javadocs предостерегает от одновременного доступа к SimpleDateFormat и того, как вы используете его, несомненно, будет включать одновременный доступ. Удаление статики не устранит проблему concurrency, если вы не применяете какой-либо тип политики синхронизации для охватывающего класса или иным образом препятствуете доступу нескольких потоков к классу.

Вы можете попытаться создать SimpleDateFormat для каждого потока, создав его в теле метода и убедиться, что ссылка на SimpleDateFormat никогда не "ускользает" от метода. Другими словами, объявите переменную, создайте экземпляр объекта и используйте объект в том же методе. Это обеспечило бы удаление ссылки для этого SimpleDateFormat при выходе метода.

Ответ 4

Лично я бы избегал всех этих проблем, просто используя JodaTime. API намного богаче, у него нет проблем с потоками, и он намного быстрее.

Ответ 5

SimpleDateFormat имеет состояние экземпляра во время разбора и, следовательно, не является потокобезопасным. Если вы используете его из нескольких потоков, он сработает (как и сбои java:-), никаких сбоев процесса и т.п.). Удаление статического ключевого слова не обязательно устраняет проблему, потому что она зависит от экземпляра и все еще может использоваться из нескольких потоков.

Вы можете создать локальный экземпляр в указанном выше методе, чтобы каждый разбор произошел с его собственным форматированием или воспроизведением с помощью переменных threadlocal.

Ответ 6

Разработчики Android могут использовать безопасные (потоковые локализованные) оболочки вокруг SimpleDateFormat, которые находятся в: org.apache.http.impl.cookie.DateUtils

Исходный код для реализации здесь (например, FROYO API Level 8):

Ответ 7

Другой способ: если вы можете гарантировать каждый раз, когда вызывается ваш контроллер, вы возвращаете новый экземпляр, а затем удаляете статическую ссылку.