Когда и как я должен использовать переменную ThreadLocal? - PullRequest
831 голосов
/ 03 мая 2009

Когда я должен использовать переменную ThreadLocal?

Как это используется?

Ответы [ 24 ]

6 голосов
/ 27 марта 2018

Начиная с выпуска Java 8, существует более декларативный способ инициализации ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

До выхода Java 8 вам приходилось делать следующее:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

Кроме того, если метод создания экземпляров (конструктор, метод фабрики) класса, который используется для ThreadLocal, не принимает никаких параметров, вы можете просто использовать ссылки на методы (представленные в Java 8):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Примечание: Оценка ленива, поскольку вы передаете java.util.function.Supplier лямбду, которая оценивается только при вызове ThreadLocal#get, но значение не было предварительно оценено.

4 голосов
/ 16 сентября 2017

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

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

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

4 голосов
/ 27 апреля 2016

когда

Когда объект не является потокобезопасным, вместо синхронизации, которая препятствует масштабируемости, передайте по одному объекту каждому потоку и оставьте для него область потока, то есть ThreadLocal. Одним из наиболее часто используемых, но не поточно-ориентированных объектов являются база данных Connection и JMSConnection.

Как?

Одним из примеров является то, что Spring Framework активно использует ThreadLocal для управления транзакциями за кулисами, сохраняя эти объекты подключения в переменных ThreadLocal. На высоком уровне, когда транзакция запускается, она получает соединение (и отключает автоматическую фиксацию) и сохраняет его в ThreadLocal. при последующих вызовах БД он использует то же соединение для связи с БД. В конце он берет соединение от ThreadLocal и фиксирует (или откатывает) транзакцию и освобождает соединение.

Я думаю, что log4j также использует ThreadLocal для поддержки MDC.

4 голосов
/ 23 октября 2012

Вы должны быть очень осторожны с паттерном ThreadLocal. Есть несколько основных недостатков, как упомянул Фил, но один из них не был упомянут, чтобы убедиться, что код, который устанавливает контекст ThreadLocal, не является «входящим».

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

3 голосов
/ 02 декабря 2015

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

Это достигается путем предоставления нового экземпляра изменяемого объекта для каждого потока попробуйте получить к нему доступ. Так что это локальная копия каждого потока. Это какой-то взломать создание переменной экземпляра в методе для доступа, как локальная переменная. Как вы знаете метод локальная переменная доступна только для потока одно отличие есть; метод локальных переменных не будет доступен потоку, когда выполнение метода закончено, где изменяемый общий объект с threadlocal будет доступен через несколько методы, пока мы не очистим это.

По определению:

Класс ThreadLocal в Java позволяет создавать переменные, которые могут быть только прочитанным и написанным тем же потоком. Таким образом, даже если два потока выполняем один и тот же код, и код имеет ссылку на Переменная ThreadLocal, тогда два потока не могут видеть друг друга ThreadLocal переменные.

Каждый Thread в Java содержит ThreadLocalMap в нем.
Где

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

Достижение ThreadLocal:

Теперь создайте класс-оболочку для ThreadLocal, который будет содержать изменяемый объект, как показано ниже (с initialValue() или без него).
Теперь методы получения и установки этой оболочки будут работать с локальным экземпляром, а не с изменяемым объектом.

Если getter () из threadlocal не нашел никакого значения с помощью в threadlocalmap Thread; тогда он вызовет initialValue (), чтобы получить свою личную копию относительно потока.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

Теперь wrapper.getDateFormatter() вызовет threadlocal.get() и будет проверять, currentThread.threadLocalMap содержит этот (threadlocal) экземпляр. Если да, вернуть значение (SimpleDateFormat) для соответствующего экземпляра локального потока
иначе добавьте карту с этим локальным экземпляром thread, initialValue ().

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

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java

3 голосов
/ 04 мая 2009

Как уже упоминалось @unknown (google), он используется для определения глобальной переменной, в которой указанное значение может быть уникальным в каждом потоке. Обычно его использование влечет за собой хранение некоторой контекстной информации, которая связана с текущим потоком выполнения.

Мы используем его в среде Java EE для передачи идентификатора пользователя классам, которые не поддерживают Java EE (не имеют доступа к HttpSession или EJB SessionContext). Таким образом, код, который использует идентификатор для операций на основе безопасности, может получить доступ к идентификатору из любого места, без необходимости явно передавать его при каждом вызове метода.

Цикл операций запрос / ответ в большинстве вызовов Java EE упрощает этот тип использования, поскольку он дает четко определенные точки входа и выхода для установки и сброса ThreadLocal.

3 голосов
/ 31 января 2013

Здесь нет ничего нового, но сегодня я обнаружил, что ThreadLocal очень полезен при использовании Bean Validation в веб-приложении. Сообщения валидации локализованы, но по умолчанию используют Locale.getDefault(). Вы можете настроить Validator с другим MessageInterpolator, но нет способа указать Locale при вызове validate. Таким образом, вы можете создать статический ThreadLocal<Locale> (или, что еще лучше, контейнер общего назначения с другими вещами, которые вам могут понадобиться, например, ThreadLocal, а затем пользовательский MessageInterpolator выберет Locale из этого. ServletFilter, который использует значение сеанса или request.getLocale(), чтобы выбрать языковой стандарт и сохранить его в справочнике ThreadLocal.

2 голосов
/ 30 ноября 2017

Локальные переменные потока часто используются для предотвращения совместного использования в проектах, основанных на изменяемые синглтоны или глобальные переменные.

Его можно использовать в сценариях, таких как создание отдельного соединения JDBC для каждого потока, когда вы не используете пул соединений.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

Когда вы вызываете getConnection, он возвращает соединение, связанное с этим потоком. То же самое можно сделать с другими свойствами, такими как dateformat, контекст транзакции, который вы не хотите разделять между потоками.

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

Эта ссылка очень хорошо объясняет использование ThreadLocal.

1 голос
/ 05 ноября 2018

[Для справки] ThreadLocal не может решить проблемы обновления общего объекта. Рекомендуется использовать объект staticThreadLocal, который используется всеми операциями в одном потоке. [Обязательный] метод remove () должен быть реализован переменными ThreadLocal, особенно при использовании пулов потоков, в которых потоки часто используются повторно. В противном случае это может повлиять на последующую бизнес-логику и вызвать непредвиденные проблемы, такие как утечка памяти.

1 голос
/ 04 июня 2018

Класс ThreadLocal в Java позволяет создавать переменные, которые могут быть прочитаны и записаны только одним потоком. Таким образом, даже если два потока выполняют один и тот же код, а код имеет ссылку на переменную ThreadLocal, эти два потока не могут видеть переменные ThreadLocal друг друга.

Подробнее

...