ThreadLocal и пул потоков

Java

вопрос

Для веб-проектов каждый раз, когда пользователь входит в систему, информация о сеансе будет сохраняться в redis, а затем возвращаться в клиентский токен, который используется как идентификатор сеанса пользователя, после чего все запросы будут проходить через перехватчик, который будет использовать токен для хранения информации о сеансе.Возьмите его из Redis и поместите в ThreadLocal. При большом количестве параллелизма в информации о сеансе будет ошибка.

Процесс анализа ошибки: Пользователь 1 входит в систему и получает доступ к системе.В это время используется поток A, и информация о сеансе пользователя 1 сохраняется в TheadLocal потока A. Затем пользователь 2, который незарегистрированный также получает доступ к системе, и сервер снова назначает поток.A обрабатывает запрос, и пользователь 2 получает информацию о сеансе пользователя 1 в это время.

ThreadLocal

TheadLocal — это полезный встроенный в поток инструмент с общими переменными, предоставляемый jdk. Во время веб-разработки серверная сторона может использовать TheadLocal для хранения запроса и ответа на запрос. Он не передается в стеке вызова метода явно. Очень удобно получать сохраненные нами переменные экземпляра в любом месте стека вызовов методов внутри потока.

class WebContext {
    private TheadLocal<HttpServletRequest> request = new TheadLocal<>();
    private TheadLocal<HttpServletResponse> response = new TheadLocal<>();
    public static void setRequest(HttpServletRequest req) {
        request.set(req);
    }
    public static HttpServletRequest getRequest() {
        request.get();
    }
    public static void setResponse(HttpServletResponse resp) {
        response.set(resp);
    }
    public static HttpServletResponse getResponse() {
        response.get();
    }
}

class AbcController {
    public Object userInfo() {
        HttpServletRequest request = WebContext.getRequest();
        // request.getParameter()
        // ...
    }
}

Взгляните на код TheadLocal:

// TheadLocal两个重要的方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

Независимо от того, является ли TheadLocal методом установки или методом получения, он сначалаThread t = Thread.currentThread();, получить текущий экземпляр потока. Затем получите экземпляр TheadLocalMap в экземпляре потока.ThreadLocalMap map = getMap(t);Вот почему получение переменных потока в одном и том же потоке приведет к получению одного и того же экземпляра.Поскольку каждый экземпляр потока хранит свои собственные переменные потока, эти переменные потокобезопасны.

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

Вышеупомянутое объявлено в классе ThreadthreadLocalsАтрибуты. Можно видеть, что фактическая переменная потока храненияThreadLocal.ThreadLocalMapКласс, в котором хранятся пары ключ-значение (Key-Value), аналогичные Map, где Key — экземпляр потока, а Value — переменная потока.

Пул потоков Tomcat

Поскольку создание потоков операционной системой очень ресурсоемко, многие приложения будут использовать пулы потоков.Инициализация системы заключается в создании определенного количества потоков за один раз, и эти потоки будут повторно использоваться и совместно использоваться, то есть потоки не будут использоваться после того, как они будут использованы, будут уничтожены до тех пор, пока приложение не будет остановлено.

В результате разные пользователи запрашивают мультиплексирование одного и того же потока, что приводит к получению одной и той же информации о сеансе разными пользователями, что приводит к неупорядоченной информации о сеансе.

решать

Настроить перехватчик на запрос前端Установите информацию о сеансе в локальные переменные потока, в后端Очистите информацию о сеансе переменной локального потока.

// 代码简化了
public class SessionInspector extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(...) throws Exception {
        // ThreadLocal.set(Session)
        return true;
    }
    
    public void afterCompletion(...) {
        // ThreadLocal.clear()
    }
}

Суммировать

ThreadLocal предоставляет нам удобный способ совместного использования переменных в потоках и обеспечивает безопасность потока данных. Но рассмотрим случай повторного использования потока в многопоточной среде, еслиThreadLocalСохраняемая информация — это сеанс и другая информация, связанная с пользователем, которая очищается в конце задачи потока.ThreadLocalинформация внутри.