Как сделать статический календарь потокобезопасным

#java #static #calendar #thread-safety

#java #статический #Календарь #потокобезопасность

Вопрос:

Я хотел бы использовать календарь для некоторых статических методов и использовать статическое поле:

 private static Calendar calendar = Calendar.getInstance();
  

Теперь я читаю java.util.Календарь не потокобезопасен. Как я могу сделать этот поток безопасным (он должен быть статичным)?

Комментарии:

1. Является ли ваш статический календарь просто фиксированной датой?

2. может быть, это помогло бы сделать его «изменчивым»?

3. нет, я просто не хочу создавать так много экземпляров — для каждого вызова моих методов, поскольку это, возможно, может занять время.

4. Вы имеете в виду, что вас беспокоит снижение производительности многих вызовов конструктора?

5. Я начинаю приходить к выводу, что Calendar не следует рассматривать просто как «не потокобезопасный». Он должен быть помечен как «враждебный потоку».

Ответ №1:

Вы не можете сделать что-то потокобезопасным, если это не так. В случае Calendar даже чтение данных из него не является потокобезопасным, поскольку может обновлять внутренние структуры данных.

Если это вообще возможно, я бы предложил использовать вместо этого время Joda:

  • Большинство типов являются неизменяемыми
  • Неизменяемые типы потокобезопасны
  • В любом случае, это, как правило, намного лучший API

Если вам абсолютно необходимо использовать Calendar , вы могли бы создать блокирующий объект и поместить весь доступ через блокировку. Например:

 private static final Calendar calendar = Calendar.getInstance();
private static final Object calendarLock = new Object();

public static int getYear()
{
    synchronized(calendarLock)
    {
        return calendar.get(Calendar.YEAR);
    }
}

// Ditto for other methods
  

Хотя это довольно неприятно. У вас мог бы быть только один синхронизированный метод, который создавал бы клон исходного календаря каждый раз, когда это было необходимо, конечно … возможно, что, вызвав computeFields or computeTime , вы могли бы сделать последующие операции чтения потокобезопасными, конечно, но лично я бы не хотел это пробовать.

Комментарии:

1. Если вы клонируете шаблон, вам не нужен синхронизированный блок. Поскольку сам шаблон неизменяем.

2. Несмотря на то, что чтение в календаре обновляет внутреннее состояние, можете ли вы привести пример, когда два потока могут попытаться установить разные значения для этого состояния?

3. @bestsss: Ну, это зависит от того, потокобезопасен ли сам clone. Учитывая отсутствие потокобезопасности в остальной части Calendar , я бы не хотел полагаться на это.

4. @Peter: Представьте, что areFieldsSet становится видимым как true для одного потока из-за вычислений в другом до того, как сами вычисляемые значения полей станут видимыми. Потенциально было бы возможно, чтобы один поток прочитал некоторые старые значения полей и некоторые новые, что привело бы к странным результатам. Мне не нравится пытаться рассуждать о деталях реализации, чтобы решить, возможно это или нет.

5. @Voo: Мой будильник можно настроить так, чтобы он будил меня в 8 часов по местному времени каждый день, где бы я ни был. Какой часовой пояс следует использовать в его представлении? 😉

Ответ №2:

Календарь потокобезопасен при условии, что вы его не меняете. Использование в вашем примере отличное.

Стоит отметить, что Calendar не является эффективным классом, и вы должны использовать его только для сложных операций (например, для поиска следующего месяца / года) ИМХО: если вы используете его для сложных операций, используйте только локальные переменные.

Если все, что вам нужно, это моментальный снимок времени, более быстрый способ — использовать currentTimeMillis, который даже создает объект. Вы можете сделать поле изменяемым, если хотите сделать его потокобезопасным.

 private static long now = System.currentTimeMillis();
  

Использование немного подозрительно. Зачем вам получать текущее время и сохранять его глобально вот так. Это напоминает мне старую шутку.

— У вас есть время?
— Да, у меня это где-то записано.

Комментарии:

1. Что именно вы подразумеваете под «изменить это», имея в виду, что чтение из него может изменить его внутреннее состояние? Я не был бы полностью удивлен, обнаружив, что две одновременные операции чтения могут потенциально привести к повреждению состояния. Это маловероятно, но я бы не хотел на это полагаться. Calendar — отвратительный, мерзкий класс : (

2. Его чтение не изменит его состояние непоследовательным образом. Если бы вы беспокоились об этом, вам пришлось бы сказать то же самое о String.hashCode().

3. Я согласен, что календарь отвратительный. Вы пробовали его сериализовать? Он отправляет всю информацию о часовом поясе, что не очень хорошо сказывается на производительности, но это означает, что если у вас есть система с актуальной информацией о часовом поясе, но вы читаете старый календарь или получаете календарь из системы, которая не является актуальной, вы получаете незначительные проблемы с часовым поясом, которых вы никогда не ожидали, из-за ошибок в коде, который вы не запускаете. 😛

4. Смотрите мой комментарий, отвечающий вам в моем ответе. String разработан таким образом, чтобы быть потокобезопасным, поэтому его хэш-кэширование потокобезопасно: значение атомарно изменяется с «некэшированного» на «кэшированное значение». Сравните это с Calendar , где boolean поле определяет, установлены ли поля, а сами поля являются отдельными. Что гарантирует, что все изменения в этом наборе данных происходят атомарно или в желаемом порядке?

5. @scottb Часть того, что замедляет работу Календаря, — это создание объектов TimeZone и Locale. Если вы кэшируете эти неизменяемые объекты, вы можете ускорить создание нового календаря. Чтобы сделать календарь потокобезопасным, вам нужно заблокировать его во время использования.

Ответ №3:

Ты не можешь. Да, вы могли бы синхронизироваться с ним, но у него все еще есть изменяемые поля состояния. Вам нужно будет создать свой собственный объект календаря.

Если возможно, используйте что-нибудь легкое, например, long, измеряющий время в миллисекундах, и конвертируйте в Календарь только тогда, когда вам это нужно.

Ответ №4:

Создайте Calendar в качестве локальной переменной в методе. Если вам нужен один и тот же календарь во всех методах, вы можете использовать статику, где (одноэлементный или квазиодноэлементный) объект был бы более подходящим.

Комментарии:

1. Вы действительно предполагаете, что создание синглтона изменяемого, не потокобезопасного объекта поможет сделать его класс потокобезопасным? Неужели?