Мое использование ThreadLocal
В моих классах Java я иногда использую ThreadLocal
главным образом как средство избежать ненужного создания объекта:
@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {
private final Date then;
public DateSensitiveThing(Date then) {
this.then = then;
}
private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>() {
@Override
protected Calendar initialValue() {
return new GregorianCalendar();
}
};
public Date doCalc(int n) {
Calendar c = threadCal.get();
c.setTime(this.then):
// use n to mutate c
return c.getTime();
}
}
Я делаю это по уважительной причине - GregorianCalendar
- это один из тех славных, изменчивых, изменяемых, не-потоковых объектов, который предоставляет услугу для нескольких вызовов, а не представляет значение. Кроме того, считается, что это "дорого" для создания экземпляра (верно ли это или нет, это не вопрос этого вопроса). (В целом, я действительно восхищаюсь этим: -))
Как Tomcat Whinges
Однако, если я использую такой класс в любой среде, которая объединяет потоки и где мое приложение не контролирует жизненный цикл этих потоков, тогда существует потенциал для утечек памяти. Хорошим примером является среда Servlet.
На самом деле, Tomcat 7 так бежит, когда останавливается webapp:
SEVERE: веб-приложение [] создало ThreadLocal с ключом типа [org.apache.xmlbeans.impl.store.CharUtil $1] (значение [[email protected]]) и значение type [java.lang.ref.SoftReference] (значение [[email protected]]), но не удалось удалить его, когда веб-приложение было остановлено. Темы будут обновлены время, чтобы попытаться избежать вероятной утечки памяти. 13 декабря 2012 г. 12:54:30 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(Даже мой код не делает этого в этом конкретном случае).
Кто виноват?
Это вряд ли справедливо. Tomcat обвиняет меня (или пользователя моего класса) за правильную работу.
В конечном счете, это потому, что Tomcat хочет повторно использовать потоки, которые он мне предложил, для других веб-приложений. (Ugh - я чувствую себя грязным.) Вероятно, это не отличная политика в отношении Tomcat - потому что потоки фактически имеют/вызывают состояние - не делятся между ними.
Однако эта политика, по крайней мере, распространена, даже если это нежелательно. Я чувствую, что я обязан - как пользователь ThreadLocal
предоставить способ для моего класса "освободить" ресурсы, которые мой класс подключил к различным потокам.
Но что с этим делать?
Какая правильная вещь здесь?
Мне кажется, что политика перенаправления потоков сервлет-движка не согласуется с намерением ThreadLocal
.
Но, может быть, я должен предоставить средство, позволяющее пользователям сказать "begone, evil thread-specific state, связанное с этим классом, хотя я не в состоянии позволить потоку умереть и позволить GC сделать свое дело?". Возможно ли мне это сделать? Я имею в виду, что я не могу организовать ThreadLocal#remove()
для вызова каждой из нитей, которые видели в прошлом ThreadLocal#initialValue()
. Или есть другой способ?
Или я должен просто сказать моим пользователям: "Идите и получите себе достойную реализацию загрузчика классов и потоков"?
EDIT # 1: Уточнено, как threadCal
используется в классе утилиты vanailla, который не знает о жизненных циклах потоков
EDIT # 2: Исправлена проблема безопасности потока в DateSensitiveThing