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

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

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

Ответы [ 24 ]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

15 голосов
/ 04 октября 2015

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

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

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

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

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

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

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

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

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

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

9 голосов
/ 28 апреля 2013

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

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

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

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

8 голосов
/ 01 июля 2013
  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 - Пример программы и учебное пособие

7 голосов
/ 20 августа 2015

Два варианта использования, где можно использовать переменную 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()));
    } 

}
...