Вопросы по использованию ThreadLocal в одноэлементном сервисе Spring
В моем классе обслуживания синглтонной области ниже все методы в классе требуют некоторого пользовательского контекста, который известен, когда Service.doA()
называется. Вместо того, чтобы передавать информацию по методам, я думал о сохранении этих значений в TheadLocal
, У меня есть два вопроса об этом подходе:
1) Использует ли реализация ниже ThreadLocal
правильно? То есть он является поточно-ориентированным, и правильные значения будут считываться / записываться в ThreadLocal
?
2) ли ThreadLocal userInfo
нужно очистить явно, чтобы предотвратить утечки памяти? Это будет мусор?
@Service
public class Service {
private static final ThreadLocal<UserInfo> userInfo = new ThreadLocal<>();
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
doB();
doC();
}
private void doB() {
// needs user info
UserInfo userInfo = userInfo.get();
}
private void doC() {
// needs user info
UserInfo userInfo = userInfo.get();
}
}
3 ответа
1) Пример кода в порядке, за исключением столкновений имен в doB и doC, где вы используете то же имя для статической переменной, ссылающейся на ThreadLocal, что и для локальной переменной, содержащей то, что вы извлекаете из ThreadLocal.
2) Объект, который вы храните в ThreadLocal, остается прикрепленным к этому потоку, пока не будет явно удален. Если ваша служба выполняется в контейнере сервлета, например, когда запрос завершается, его поток возвращается в пул. Если вы не очистили содержимое переменной ThreadLocal потока, то эти данные будут зависеть, сопровождая любой запрос, который поток будет выделен для следующего. Каждый поток является корнем GC, локальные переменные потока, присоединенные к потоку, не будут собирать мусор до тех пор, пока поток не умрет. Согласно документу API:
Каждый поток содержит неявную ссылку на свою копию локальной переменной потока, пока поток жив и экземпляр ThreadLocal доступен; после того, как поток уходит, все его копии локальных экземпляров потока подлежат сборке мусора (если не существует других ссылок на эти копии).
Если ваша контекстная информация ограничена областью действия одной службы, вам лучше передавать информацию через параметры, а не использовать ThreadLocal. ThreadLocal предназначен для случаев, когда информация должна быть доступна в разных сервисах или на разных уровнях, кажется, что вы слишком усложняете свой код, если он будет использоваться только одним сервисом. Теперь, если у вас есть данные, которые будут использоваться советом AOP для различных разнородных объектов, размещение этих данных в локальном потоке может быть допустимым использованием.
Чтобы выполнить очистку, обычно вы определяете точку, в которой поток завершается с текущей обработкой, например, в фильтре сервлета, где переменная threadlocal может быть удалена до того, как поток будет возвращен в пул потоков. Вы не будете использовать блок try-finally, потому что место, куда вы вставляете объект threadlocal, совсем близко от того места, где вы его очищаете.
Когда вы используете ThreadLocal
Вы должны убедиться, что вы очищаете это, что бы ни случилось, потому что:
- Это как-то создает утечку памяти, так как значение не может быть собрано GC, потому что объект подходит для GC тогда и только тогда, когда больше не существует объекта, который имеет жесткую ссылку на объект прямо или косвенно. Так, например, здесь, ваш
ThreadLocal
экземпляр косвенно имеет жесткую ссылку на вашу ценность, хотя его внутреннийThreadLocalMap
Единственный способ избавиться от этой трудной ссылки - позвонитьThreadLocalMap#remove()
как это будет удалить значение изThreadLocalMap
, Другим потенциальным способом сделать вашу ценность приемлемой для GC будет случай, когда вашThreadLocal
Экземпляр сам по себе подходит для GC, но здесь он является константой в классеService
таким образом, он никогда не будет иметь право на GC, чего мы и хотим в вашем случае. Таким образом, единственный ожидаемый способ - позвонитьThreadLocalMap#remove()
, - Это создает ошибки, которые трудно найти, потому что большую часть времени поток, который использует ваш
ThreadLocal
является частьюthread pool
так что поток будет повторно использован для другого запроса, так что если вашThreadLocal
не был должным образом очищен, поток будет повторно использовать экземпляр вашего объекта, хранящийся в вашемThreadLocal
это потенциально даже не связано с новым запросом, который приводит к сложным ошибкам. Так вот, например, мы могли бы получить результат другого пользователя только потому, чтоThreadLocal
не был очищен
Итак, картина следующая:
try {
userInfo.set(new UserInfo(userId, name));
// Some code here
} finally {
// Clean up your thread local whatever happens
userInfo.remove();
}
Что касается безопасности потоков, это, конечно, потокобезопасно, даже если UserInfo
не является потокобезопасным, потому что каждый поток будет использовать свой собственный экземпляр UserInfo
так что ни одного случая UserInfo
хранится в ThreadLocal
будет доступен или изменен несколькими потоками, потому что ThreadLocal
значение каким-то образом ограничено текущим потоком.
Это определенно должно быть убрано после использования. ThreadLocals очень легко теряют память, как утечка памяти, так и памяти permgen/metaspace из-за утечки классов. В вашем случае лучший способ будет:
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
try {
doB();
doC();
} finally {
userInfo.remove()
}
}