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

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

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


person Community    schedule 03.05.2009    source источник
comment
Если вы используете ThreadLocal, никогда не пишите над ним оболочку !!! Каждый разработчик, который хочет использовать переменную, должен знать, что это ThreadLocal.   -  person Not a bug    schedule 11.02.2016
comment
@Notabug Если вы явно указали, вы можете назвать свою переменную так, чтобы вы имели дело со значением ThreadLocal, например RequestUtils.getCurrentRequestThreadLocal(). Я не говорю, что это очень элегантно, но это связано с тем, что сам ThreadLocal в большинстве случаев не очень элегантен.   -  person whirlwin    schedule 30.03.2017


Ответы (25)


Одно из возможных (и распространенных) вариантов использования - это когда у вас есть объект, который не является потокобезопасным, но вы хотите избежать синхронизация доступа к этому объекту (я смотрю на вас, 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);
    }
}

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

person overthink    schedule 03.05.2009
comment
Другая альтернатива синхронизации или threadlocal - сделать переменную локальной. Локальные вары всегда ориентированы на многопотоковое исполнение. Я предполагаю, что делать DateFormats локальными - плохая практика, потому что их создание дорого, но я никогда не видел надежных показателей по этой теме. - person Julien Chastang; 04.05.2009
comment
Вот полный пример, показывающий, что разные потоки имеют свои собственные копии. journaldev.com/1076/ - person Pankaj; 31.12.2012
comment
Я только что столкнулся с проблемами параллелизма даты / календаря / формата. Хотя я быстро понял, что происходит, в вашем сообщении было ясно и просто решить мои проблемы без капитального ремонта моего кода. Спасибо! Лично я решил вызывать ThreadLocal remove после каждого get (), в идеале в предложении finally. Есть мысли, если это перебор? Я хотел убедиться, что память может быть освобождена после выполнения включающего Thread. - person Friedryk; 11.01.2013
comment
Это высокая цена за взлом SimpleDateFormat. Возможно, лучше использовать поточно-ориентированную альтернативу. Если вы согласны с тем, что синглтоны - это плохо, то ThreadLocal еще хуже. - person Alexander Ryzhov; 27.06.2013
comment
@overthink Есть ли причина объявлять ThreadLocal статическим и окончательным, я имею в виду производительность или что-то в этом роде? - person Sachin Gorade; 04.08.2015
comment
@SachinGorade - это всего лишь один экземпляр средства форматирования для каждого загрузчика классов. В среде с одним основным загрузчиком классов, который работает как синглтон. - person Renascienza; 11.08.2015
comment
Синглтоны хороши или плохи не во всех ситуациях. Вкратце: основная цель синглтона - минимизировать создание дополнительных объектов, желательно только одного. В средах с несколькими загрузками классов (например, в контейнерах веб-приложений) они почти бесполезны. Их сложно тестировать или использовать при тестировании. Вам нужна стратегия, с которой можно справиться. - person Renascienza; 11.08.2015
comment
Метод ThreadLocal.get () вызывает ThreadLocal.initialValue () (один раз) для каждого потока, что означает, что объект SimpleDateFormat создается для каждого потока. Разве тогда не лучше просто иметь SimpleDateFormat в качестве локальных переменных (поскольку нам не нужно решать проблемы со сборкой мусора)? - person sudeepdino008; 24.06.2016
comment
@ sudeepdino008 нет, потому что новый SimpleDateFormat должен создаваться при каждом вызове метода, что глупо - person Enerccio; 01.07.2016
comment
Просто будьте осторожны, чтобы не использовать инициализацию двойных скобок для SimpleDateFormat, потому что это создаст анонимный класс, поэтому ваш загрузчик классов не сможет быть собран с помощью сборщика мусора. утечка памяти: return new SimpleDateFormat () {{applyPattern (yyyyMMdd HHmm)}}; - person user1944408; 27.07.2016
comment
@AlexanderRyzhov, Поточно-ориентированная альтернатива - это еще более высокая цена, поскольку вам нужно платить накладные расходы на синхронизацию при каждом вызове метода. Вместо этого используйте локальную переменную. - person Pacerier; 19.09.2017
comment
@Enerccio, Dude, переменная-член в контексте каждого экземпляра объекта. Он может быть доступен из всех вызовов методов. - person Pacerier; 19.09.2017
comment
@Pacerier, как связаны переменные-члены? Он говорил о локальной переменной, что означает, что при каждом вызове метода будет создаваться новый SimpleDateFormat, что является пустой тратой. Локальный поток намного лучше, потому что он гибок, а утечка памяти практически не заметна, и ею можно управлять, когда поток в любом случае уничтожен. Переменная-член SimpleDateFormat - это плохо, потому что она не является потокобезопасной! - person Enerccio; 20.09.2017
comment
@Pacerier, зачем вам синхронизация с ThreadLocal? - person Enerccio; 20.09.2017
comment
но у этого решения есть собственная проблема, например, ваши объекты все еще существуют в ThreadPool, и это может вызвать проблему с OOM, если вы собираетесь хранить разные объекты в ThreadLocal и ваше приложение получает много запросов. Я видел это как решение для передачи объекта другим методам без передачи его в качестве параметра. Итак, вы можете создать объект в ThreadLocal, а затем удалить его, чтобы очистить его. - person Vasif; 13.08.2019

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

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

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

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

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

person Phil M    schedule 03.05.2009
comment
как вы думаете, этого можно было бы избежать, используя SoftReference? - person Ray; 14.02.2011
comment
Алекс Вассер переместил свой блог. Здесь текущая ссылка на статью об утечке памяти. . - person Kenster; 02.05.2012
comment
Поток, на который ссылается Жюльен, переместился на здесь кажется, и его стоит прочитать… - person Donal Fellows; 11.06.2013
comment
Это очень много положительных отзывов за ответ, который, хотя и информативен, никоим образом не отвечает на вопрос. - person Robin; 16.04.2014
comment
Или просто перезапустите свой узел вместо горячего развертывания. (в большинстве случаев я уверен, что есть другие, где вам просто нужно обернуть мягкую ссылку). Но вы увидите большое сокращение используемых объектов и времени, если вы используете локальные переменные потока или область весеннего потока (повторно используемую пользователями, поскольку пул потоков для веб-запросов является долгоживущим) вместо заполнения каждой службы в области сеанса. - person tgkprog; 12.08.2015
comment
Теперь, когда PermGen убит Java 8, меняет ли это каким-либо образом этот ответ? - person Evil Washing Machine; 15.09.2015
comment
Если ThreadPool поддерживает потоки, их ThreadLocals тоже будут, но они будут перезаписаны новыми значениями, когда поток в пуле будет снова выделен, нет? Почему это утечка? Мне просто кажется, что вы могли бы исправить увеличение памяти, возможно, равное num thread * size для threadlocal value, но ваш ThreadPool должен управлять собой до разумного размера, так что на самом деле это не утечка памяти, увеличивая MaxPermSize на самом деле должно быть хорошим решением. - person Didier A.; 11.06.2016
comment
Я считаю, что ответ больше не актуален, потому что Thread использует WeakReference<ThreadLocal>. Поэтому, когда связанный ThreadLocal gced, карта также удалит устаревшие значения во время следующего повторного хеширования. - person Konstantin Milyutin; 26.10.2016
comment
@Robin: Я не согласен. Вопрос заключается в том, как правильно использовать ThreadLocal, чтобы получить полное представление о любой концепции (ThreadLocal здесь), также важно понимать, как не использовать его и риски небрежного использования, и именно об этом отвечает Фил. Я рад, что он затронул этот вопрос, который не рассматривается ни в каких других ответах. Все эти голоса заслужены. Я считаю, что SO должна формировать понимание концепций, а не просто быть сайтом QA. - person Saurabh Patil; 26.01.2017
comment
Ответ @Robin не обязательно должен быть в точности тем, что сказал другой человек, если что-то уже сказано, дополнительная информация более ценна, чем повторение того же самого. И здесь ответ содержит качественную и важную информацию. - person prnjn; 29.05.2019

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

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

person Esko Luontola    schedule 04.05.2009
comment
Именно так фреймворк exPOJO (www.expojo.com) обеспечивает доступ к ORM Session / PersistenceManager без дополнительных затрат на аннотации и инъекции. Это что-то вроде «внедрения потока» вместо «внедрения объекта». Он обеспечивает доступ к зависимостям без необходимости (и накладных расходов) их встраивания в каждый объект, которому могут понадобиться эти зависимости. Удивительно, насколько «легким» можно сделать структуру DI, когда вы используете внедрение потока вместо классического DI (например, Spring и т. Д.) - person Volksman; 27.04.2013
comment
Почему мне пришлось прокручивать страницу вниз, чтобы найти этот важный ответ !? - person Jeremy Stein; 05.11.2014
comment
@Esko, в вашем проекте использование ThreadLocal в хэш-коде и равно не требуется. Лучшим подходом является создание или передача ссылки на функцию hashcode / equals всякий раз, когда это необходимо. Таким образом, вы можете полностью избежать необходимости взлома ThreadLocal. - person Pacerier; 19.09.2017
comment
@JeremyStein, лучшие ответы на stackoverflow.com/a/817911/632951 и stackoverflow.com/a/17398338/632951 и stackoverflow.com/ a / 818364/632951 - person Pacerier; 19.09.2017
comment
Это лучшее объяснение использования TL. - person Dexter; 15.03.2018

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

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

person user100464    schedule 03.05.2009
comment
Поэтому вам следует избегать локальных переменных потоков так же, как и глобальных переменных. Я не могу согласиться с тем, что можно создавать глобальные объекты (локальные потоки) вместо передачи значений, людям не нравится, потому что это часто выявляет проблемы архитектуры, которые они не хотят исправлять. - person Juan Mendes; 12.07.2013
comment
Возможно ... Однако это может быть полезно, если у вас есть огромная существующая кодовая база, в которую вам нужно добавить новые данные, которые нужно передавать везде, например контекст сеанса, транзакция БД, вошедший в систему пользователь и т. д. - person Cornel Masson; 23.07.2014
comment
Престижность за использование данных вместо данных. - person deep; 21.06.2016
comment
Это вызвало у меня любопытство. 'datum' is the singular form and 'data' is the plural form. - person Siddhartha; 03.06.2020

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

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

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

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

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

person Rakesh Chauhan    schedule 04.10.2015

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

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

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

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

person Neil Coffey    schedule 03.05.2009

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

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

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

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

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

person Abhijit Gaikwad    schedule 01.07.2013

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

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

person RichieHindle    schedule 03.05.2009
comment
По умолчанию статические объекты или объекты, явно передаваемые между потоками, когда оба потока имеют ссылку на один и тот же объект, являются общими. Объекты, объявленные локально в потоке, не являются общими (они локальны для стека потока). Просто хотел это прояснить. - person Ian Varley; 26.05.2010
comment
@IanVarley: Спасибо, что указали на это. Итак, если у нас есть переменная, которая объявлена ​​и используется в классе MyThread, тогда нужен ли нам ThreadLocal в этом случае? Пример: class MyThread расширяет Thread {String var1 ;, int var2; } в этом случае var1 и var2 будут частью собственного стека Thread и не будут совместно использоваться, поэтому нужен ли нам ThreadLocal в этом случае? Я могу совершенно ошибаться в своем понимании, пожалуйста, помогите. - person Jayesh; 14.06.2016
comment
Если каждый поток хочет иметь свою собственную копию чего-либо, почему его нельзя просто объявить локальным (что всегда является потокобезопасным)? - person sudeepdino008; 24.06.2016
comment
@ sudeepdino008 у вас не всегда есть контроль над созданием этих потоков (фреймворки webapp, библиотеки threadpool) - person JMess; 25.04.2018

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

person znlyj    schedule 28.04.2013

Два случая использования, в которых может использоваться переменная 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's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's 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()));
    } 

}
person infoj    schedule 20.08.2015
comment
Я все еще не видел преимущества использования ThreadLocal для генерации уникального идентификатора в первом примере, если вся ваша цель - вернуть инкрементное уникальное целочисленное значение. `` `public class ThreadId {// Атомарное целое число, содержащее идентификатор следующего потока, которому будет назначен частный статический final AtomicInteger nextId = new AtomicInteger (0); // Возвращает уникальный идентификатор текущего потока, при необходимости присваивая его public static int get () {return nextId..getAndIncrement (); }} `` ` - person dashenswen; 25.04.2019

Начиная с выпуска 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, но значение ранее не оценивалось.

person Andrii Abramov    schedule 27.03.2018

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

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

person Jeff Richley    schedule 23.10.2012
comment
Повторный вход не является проблемой, если код готов с этим справиться. При входе отметьте, установлена ​​ли уже переменная, а при выходе восстановите ее предыдущее значение (если оно было) или удалите его (если нет). - person supercat; 18.03.2014
comment
@Jeff, Чувак, это верно для каждого кода, который вы пишете, а не только для шаблона ThreadLocal. Если вы сделаете F(){ member=random(); F2(); write(member); }, а F2 переопределит член с новым значением, то очевидно, что write(member) больше не будет записывать номер, который вы random()ed. Это буквально просто здравый смысл. Точно так же, если вы сделаете F(){ F(); }, то удачи вам с вашим бесконечным циклом! Это верно везде, а не только для ThreadLocal. - person Pacerier; 19.09.2017

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()).
Теперь методы получения и установки этой оболочки будут работать с экземпляром 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's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

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

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

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

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

person Kanagavelu Sugumar    schedule 02.12.2015

когда?

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

Как ?

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

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

person Limestone    schedule 27.04.2016

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

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

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

person Dimos    schedule 16.09.2017
comment
ThreadLocals НЕ являются глобальным состоянием, если вы не сделаете его глобальным состоянием. Они практически всегда являются доступным ресурсом стека. Вы можете имитировать локальный поток, просто передав эту переменную во все ваши методы (что отстает); это не делает его глобальным состоянием ... - person Enerccio; 20.09.2017
comment
Это форма глобального состояния в том смысле, что оно доступно из любого места кода (в контексте того же потока). Это влечет за собой все последствия, такие как невозможность понять, кто читает и записывает это значение. Использование параметра функции не является чем-то отсталым, это хорошая практика, продвигающая чистые интерфейсы. Однако я согласен с вами в том, что передача параметра по всей глубине вашей кодовой базы - это запах кода. Но я также считаю, что во многих случаях использование ThreadLocal - это в первую очередь запах кода, который привел вас сюда, так что это то, что следует пересмотреть. - person Dimos; 20.09.2017
comment
Он может быть просто частью состояния объекта, его не нужно использовать глобально. Конечно, вы получите небольшие накладные расходы, но если несколько объектов должны иметь разное состояние для каждого потока, вы можете использовать ThreadLocal в качестве поля объекта ... - person Enerccio; 20.09.2017
comment
Ты прав. Я, наверное, наткнулся на несколько случаев неправильного использования ThreadLocal, где он был доступен по всему миру. Как вы сказали, его все еще можно использовать как поле класса, ограничивающее видимость. - person Dimos; 20.09.2017

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

Сценарии

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

Сценарии пула потоков

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

В этом сценарии в пуле потоков, если у нас есть 4 потока, каждый из которых имеет время 1000 задач, тогда у нас есть
4000 SimpleDateFormat < strong> объект создан и ожидает, пока сборщик мусора сотрет их

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

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

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

https://www.youtube.com/watch?v=sjMe9aecW_A

person mohsen.nour    schedule 07.10.2018

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

person Colselaw    schedule 31.01.2013

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

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

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

person Robin    schedule 04.05.2009

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

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

person bpjoshi    schedule 29.11.2017
comment
В этом примере основная проблема: кто отвечает за закрытие соединения? Вместо того, чтобы создавать это соединение в качестве значения initiali, позвольте потребителю соединения явно создать соединение и привязать его к потоку через ThreadLocal. Создатель соединения также несет ответственность за закрытие. В этом примере это неясно. Создание и привязка соединения также может быть скрыто в простой структуре с помощью чего-то вроде Transaction.begin () и Transaction.end (). - person Rick-Rainer Ludwig; 07.02.2018

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

person Ian Ringrose    schedule 15.06.2015

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's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's 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();

}
}
person Ajay Kumar    schedule 30.05.2017

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

Подробнее

person Pritesh Patel    schedule 04.06.2018

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

person Young    schedule 05.11.2018

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

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

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

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

person Dev Amitabh    schedule 12.12.2017

Попробуйте этот небольшой пример, чтобы почувствовать переменную ThreadLocal:

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


Вывод в консоль, если поток BookA выполняется первым:
Результат BookA: 'wordA1, wordA2, wordA3'.
Результат BookB: 'wordB1, wordB2'.
< br> Вывод в консоль, если поток BookB выполняется первым:
Результат BookB: 'wordB1, wordB2'.
Результат BookA: 'wordA1, wordA2, wordA3'.

person DigitShifter    schedule 15.02.2020