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

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

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

Ответ 1

Одно из возможных (и распространенных) использования - это когда у вас есть объект, который не является потокобезопасным, но вы хотите избежать синхронизации доступа к этот объект (я смотрю на вас, SimpleDateFormat). Вместо этого дайте каждому потоку собственный экземпляр объекта.

Например:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Документация.

Ответ 2

Поскольку ThreadLocal является ссылкой на данные в данном Thread, вы можете столкнуться с утечками при ThreadLocal классов при использовании ThreadLocal на серверах приложений с использованием пулов потоков. Вы должны быть очень осторожны при очистке любого ThreadLocal вы get() или set(), используя метод ThreadLocal remove().

Если вы не выполните очистку, когда закончите, все ссылки на классы, загруженные как часть развернутого веб-приложения, останутся в постоянной куче и никогда не будут собирать мусор. Повторное развертывание/удаление веб-приложения не приведет к очистке каждой ссылки Thread на класс вашего веб-приложения, поскольку Thread не принадлежит вашему веб-приложению. Каждое последующее развертывание создаст новый экземпляр класса, который никогда не будет собирать мусор.

В результате вы получите исключения из java.lang.OutOfMemoryError: PermGen space памяти из-за java.lang.OutOfMemoryError: PermGen space и после некоторого java.lang.OutOfMemoryError: PermGen space Google, вероятно, просто увеличится -XX:MaxPermSize вместо исправления ошибки.

Если вы в конечном итоге столкнулись с этими проблемами, вы можете определить, какой поток и класс сохраняют эти ссылки, с помощью Eclipse Memory Analyzer и/или следуя руководству и последующим указаниям Frank Kieviet.

Обновление: вновь обнаружил запись в блоге Алекса Вассера, которая помогла мне отследить некоторые проблемы с ThreadLocal которые у меня были.

Ответ 3

Многие фреймворки используют ThreadLocals для поддержки некоторого контекста, связанного с текущим потоком. Например, когда текущая транзакция хранится в ThreadLocal, вам не нужно передавать ее в качестве параметра через каждый вызов метода, если кому-то из стека требуется доступ к нему. Веб-приложения могут хранить информацию о текущем запросе и сеансе в ThreadLocal, чтобы приложение имело к ним легкий доступ. С Guice вы можете использовать ThreadLocals при реализации пользовательских областей действия для внедренных объектов (области сервлетов Guice по умолчанию, скорее всего, также используют их).

ThreadLocals - это один из видов глобальных переменных (хотя и немного менее злой, потому что они ограничены одним потоком), поэтому вы должны быть осторожны при их использовании, чтобы избежать нежелательных побочных эффектов и утечек памяти. Разработайте свои API так, чтобы значения ThreadLocal всегда автоматически очищались, когда они больше не нужны, и что неправильное использование API будет невозможно (например, вот так). ThreadLocals могут быть использованы для того, чтобы сделать код чище, и в некоторых редких случаях они являются единственным способом заставить что-то работать (у моего текущего проекта было два таких случая; они описаны здесь в разделе "Статические поля и глобальные переменные").

Ответ 4

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

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

Ответ 5

В книге Java Concurrency in Practice есть очень хороший пример. Где автор (Джошуа Блох) объясняет, как ограничение потоков является одним из самых простых способов обеспечения безопасности потоков, а ThreadLocal является более формальным средством поддержания ограничения потоков. В конце он также объясняет, как люди могут злоупотреблять им, используя его как глобальные переменные.

Я скопировал текст из упомянутой книги, но код 3.10 отсутствует, так как не так важно понимать, где следует использовать ThreadLocal.

Локальные переменные потока часто используются для предотвращения совместного использования в проектах, основанных на изменчивых синглетонах или глобальных переменных. Например, однопоточное приложение может поддерживать глобальное соединение с базой данных, которое инициализируется при запуске, чтобы избежать необходимости передавать Соединение каждому методу. Поскольку соединения JDBC могут быть не поточно-ориентированными, многопоточное приложение, использующее глобальное соединение без дополнительной координации, также не является поточно-ориентированным. Используя ThreadLocal для хранения соединения JDBC, как в ConnectionHolder в Листинге 3.10, каждый поток будет иметь свое собственное соединение.

ThreadLocal широко используется при реализации каркасов приложений. Например, контейнеры J2EE связывают контекст транзакции с исполняющим потоком на время вызова EJB. Это легко реализовать с помощью статического Thread-Local, содержащего контекст транзакции: когда код платформы должен определить, какая транзакция выполняется в данный момент, он выбирает контекст транзакции из этого ThreadLocal. Это удобно тем, что уменьшает необходимость передавать информацию о контексте выполнения в каждый метод, но связывает любой код, который использует этот механизм, с платформой.

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

Ответ 6

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

Типичный случай - когда какой-то другой фреймворк создал поток, в котором выполняется ваш код, например, контейнер сервлета, или когда просто имеет смысл использовать ThreadLocal, потому что ваша переменная тогда "на своем логическом месте" (а не переменная висит в подклассе Thread или в другой хэш-карте).

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

Некоторые люди рекомендуют использовать ThreadLocal как способ присоединения "идентификатора потока" к каждому потоку в определенных параллельных алгоритмах, где вам нужен номер потока (см., Например, Herlihy & Shavit). В таких случаях убедитесь, что вы действительно получаете выгоду!

Ответ 7

Документация говорит об этом очень хорошо: "каждый поток, который обращается к [локальной локальной переменной] (через метод get или set), свою собственную, независимо инициализированную копию переменной".

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

Ответ 8

Сервер Webapp может содержать пул потоков, а ThreadLocal var должен быть удален до ответа клиенту, поэтому текущий поток может быть повторно использован следующим запросом.

Ответ 9

  1. ThreadLocal в Java был представлен в JDK 1.2, но позднее был обобщен в JDK 1.5 для обеспечения безопасности типов в переменной ThreadLocal.

  2. ThreadLocal может быть связан с областью действия Thread, весь код, который выполняется Thread, имеет доступ к переменным ThreadLocal, но два потока не могут видеть друг друга переменную ThreadLocal.

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

  4. Переменные ThreadLocal в Java, как правило, являются частными статическими полями в классах и поддерживают свое состояние внутри Thread.

Подробнее: ThreadLocal в Java - пример программы и учебник

Ответ 10

Два варианта использования, в которых можно использовать переменную threadlocal -
1- Когда у нас есть требование связать состояние с потоком (например, идентификатором пользователя или идентификатором транзакции). Обычно это происходит с веб-приложением, что каждый запрос на сервлет имеет уникальный идентификатор транзакции, связанный с ним.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

Обратите внимание, что здесь метод withInitial реализуется с использованием выражения лямбда.
2- Другой вариант использования - это когда мы хотим иметь потокобезопасный экземпляр, и мы не хотим использовать синхронизацию, так как стоимость исполнения с синхронизацией больше. Одним из таких случаев является использование SimpleDateFormat. Поскольку SimpleDateFormat не является потокобезопасным, поэтому мы должны обеспечить механизм, чтобы сделать его потокобезопасным.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

Ответ 11

Начиная с выпуска 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 лямбда- java.util.function.Supplier который вычисляется только при ThreadLocal#get но значение ранее не оценивалось.

Ответ 12

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

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

Ответ 13

когда?

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

Как?

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

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

Ответ 14

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

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

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

Ответ 15

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

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

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

Ответ 16

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

Ответ 17

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 и setter этой оболочки будут работать с экземпляром threadlocal вместо изменяемого объекта.

Если 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 copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

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

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

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

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

Ответ 18

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

Он может использоваться в сценариях, таких как создание отдельного соединения 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 очень хорошо.

Ответ 19

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

Прочитайте больше

Ответ 20

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

Ответ 21

Кэширование, иногда вам приходится вычислять одно и то же значение много времени, поэтому, сохраняя последний набор входов для метода, и результат вы можете ускорить код. Используя Thread Local Storage, вы избегаете думать о блокировке.

Ответ 22

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

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

Ответ 23

Threadlocal обеспечивает очень простой способ достижения повторного использования объектов с нулевой стоимостью.

У меня была ситуация, когда несколько потоков создавали образ изменяемого кэша при каждом уведомлении об обновлении.

Я использовал Threadlocal в каждом потоке, а затем каждому потоку нужно было просто сбросить старое изображение и затем обновлять его из кэша при каждом уведомлении об обновлении.

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

Ответ 24

Есть 3 сценария для использования помощника класса как SimpleDateFormat в многопоточном коде, лучший из которых это использование ThreadLocal

Сценарии

1- Использование подобного общего объекта с помощью механизма блокировки или синхронизации, который замедляет работу приложения

2- Использование в качестве локального объекта внутри метода

В этом случае, если у нас есть 4 потока, каждый из которых вызывает метод 1000 раз, то мы имеем
4000 SimpleDateFormat объект создан и ждет GC, чтобы стереть их

3- Использование ThreadLocal

если у нас есть 4 потока, и мы дали каждому потоку один экземпляр SimpleDateFormat
Таким образом, у нас есть 4 потока, 4 объекта SimpleDateFormat.

Нет необходимости в механизме блокировки, создании и уничтожении объектов. (Хорошая временная сложность и пространственная сложность)