Вопросы по использованию 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 Вы должны убедиться, что вы очищаете это, что бы ни случилось, потому что:

  1. Это как-то создает утечку памяти, так как значение не может быть собрано GC, потому что объект подходит для GC тогда и только тогда, когда больше не существует объекта, который имеет жесткую ссылку на объект прямо или косвенно. Так, например, здесь, ваш ThreadLocal экземпляр косвенно имеет жесткую ссылку на вашу ценность, хотя его внутренний ThreadLocalMapЕдинственный способ избавиться от этой трудной ссылки - позвонить ThreadLocalMap#remove() как это будет удалить значение из ThreadLocalMap, Другим потенциальным способом сделать вашу ценность приемлемой для GC будет случай, когда ваш ThreadLocal Экземпляр сам по себе подходит для GC, но здесь он является константой в классе Service таким образом, он никогда не будет иметь право на GC, чего мы и хотим в вашем случае. Таким образом, единственный ожидаемый способ - позвонить ThreadLocalMap#remove(),
  2. Это создает ошибки, которые трудно найти, потому что большую часть времени поток, который использует ваш 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()
  }
}
Другие вопросы по тегам