Статьи

Потоки историй: ThreadLocal в веб-приложениях

На этой неделе я трачу разумное время на устранение всех наших переменных ThreadLocal в наших веб-приложениях. Причиной было то, что они создали утечки из загрузчика классов, и мы больше не могли должным образом удалять наши приложения. Утечка загрузчика классов происходит, когда корень GC продолжает ссылаться на объект приложения после того, как приложение было развернуто. Если на объект приложения все еще ссылаются после отмены развертывания, тогда загрузчик класса не может быть собран мусором, поскольку рассматриваемый объект ссылается на файл класса вашего приложения, который, в свою очередь, ссылается на загрузчик классов. Это вызовет OutOfMemoryError после того, как вы OutOfMemoryError и OutOfMemoryError развернули пару раз.

ThreadLocal — один из классических кандидатов, который может легко создавать утечки загрузчика классов в веб-приложениях. Сервер управляет своими потоками в пуле. Эти темы живут дольше, чем ваше веб-приложение. На самом деле они не умирают вообще, пока не умирает базовая JVM. Теперь, если вы помещаете ThreadLocal в объединенный поток, который ссылается на объект вашего класса, вы * должны * быть осторожны. Вы должны убедиться, что эта переменная снова удалена с помощью ThreadLocal.remove() . Проблема в веб-приложениях: где находится правильное место для безопасного удаления переменных ThreadLocal ? Кроме того, вы можете не захотеть изменять этот «код удаления» каждый раз, когда коллега решил добавить еще один ThreadLocal в управляемые потоки.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ThreadLocalUtil {
 
    private final static ThreadLocal<ThreadVariables> THREAD_VARIABLES = new ThreadLocal<ThreadVariables>() {
 
        /**
         * @see java.lang.ThreadLocal#initialValue()
         */
        @Override
        protected ThreadVariables initialValue() {
            return new ThreadVariables();
        }
    };
 
    public static Object getThreadVariable(String name) {
        return THREAD_VARIABLES.get().get(name);
    }
 
    public static Object getThreadVariable(String name, InitialValue initialValue) {
        Object o = THREAD_VARIABLES.get().get(name);
        if (o == null) {
            THREAD_VARIABLES.get().put(name, initialValue.create());
            return getThreadVariable(name);
        } else {
            return o;
        }
    }
 
    public static void setThreadVariable(String name, Object value) {
        THREAD_VARIABLES.get().put(name, value);
    }
 
    public static void destroy() {
        THREAD_VARIABLES.remove();
    }
}
 
public class ThreadVariables extends HashMap<String, Object> { }
 
public abstract class InitialValue {
 
    public abstract Object create();
 
}

Преимущество служебного класса заключается в том, что разработчику не нужно индивидуально управлять жизненным циклом локальной переменной потока. Класс помещает все локальные потоки в одну карту переменных. Метод destroy() может быть вызван, где вы можете безопасно удалить все локальные потоки в вашем веб-приложении. В нашем случае это ServletRequestListener -> requestDestroyed() . Вам также нужно будет разместить блоки в другом месте. Типичные места находятся рядом с HttpServlet , в методах init() , doPost() , doGet() . Это может удалить все локальные потоки в рабочих потоках пула после выполнения запроса или непредвиденного исключения. Иногда случается, что main поток сервера пропускает локальные переменные потока. Если это так, вам нужно найти правильные места для вызова метода ThreadLocalUtil -> destroy() . Для этого выясните, где основной поток фактически * создает * переменные потока. Вы можете использовать свой отладчик, чтобы сделать это.

Многие парни предлагают исключить ThreadLocal в веб-приложениях по нескольким причинам. Их может быть очень трудно удалить в среде потоков с пулами, чтобы можно было безопасно удалить приложения. Переменные ThreadLocal могут быть полезны, но справедливо рассмотреть другие методы перед их применением. Альтернативой для веб-приложений для переноса параметров области запроса является HttpServletRequest . Многие веб-фреймворки допускают общий доступ к параметрам запроса, а также доступ к атрибутам запроса / сеанса, без связи с собственным API сервлетов / портлетов. Также многие фреймворки для поддержки фреймворков с запросами поддержки должны быть внедрены в дерево объектов с помощью внедрения зависимостей. Все эти параметры отвечают большинству требований и должны быть рассмотрены до использования ThreadLocal .

Ссылка: Threading истории: ThreadLocal в веб-приложениях от нашего партнера JCG Никласа.