Пример Java ThreadLocal: когда и как использовать?

Сегодня одним из наиболее важных аспектов параллельного приложения являются общие данные. Когда вы создаете поток, реализующий интерфейс Runnable, а затем запускаете различные объекты Thread, используя тот же объект Runnable. Все потоки совместно используют одни и те же атрибуты, которые определены внутри объекта runnable.

По сути, это означает, что если вы измените любой атрибут в потоке, все потоки будут затронуты этим изменением и увидят измененное значение первого потока. Иногда это желаемое поведение, например, несколько потоков увеличивают/уменьшают одну и ту же переменную счетчика, но иногда вы хотите убедиться, что каждый поток ДОЛЖЕН работать над своей копией экземпляра потока и не влияет на данные других.

 private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);Выполняемая задача1 =() -> {threadLocalValue.set(100);// сделать какую-нибудь работуSystem.out.println("Значение: " + threadLocalValue.get());};Выполняемая задача2 =() -> {threadLocalValue.set(200);// сделать какую-нибудь работуSystem.out.println("Значение: " + threadLocalValue.get());};Поток поток1 = новый поток(задача1, "Поток-1");Поток поток2 = новый поток(задача2, "Поток-2");thread1.start(); // Выводит 100thread2.start(); // Выводит 200

1. Когда следует использовать ThreadLocal?

Например, предположим, что вы работаете над приложением электронной коммерции. У вас есть требование генерировать уникальный идентификатор транзакции для каждого запроса клиента в процессе контроллера. Вам необходимо передать этот идентификатор транзакции в бизнес-методы в классах менеджера/DAO для целей регистрации.

Одним из решений может быть передача этого идентификатора транзакции в качестве параметра всем бизнес-методам. Но это не хорошее решение, так как код избыточный и ненужный.

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

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

Связано: Обмен контекстными данными с помощью JAX-RS ResteasyProviderFactory(пример ThreadLocalStack)

2. Как работает класс ThreadLocal?

Java Concurrency API предоставляет понятный механизм для локальных переменных потока с использованием класса ThreadLocal с очень хорошей производительностью.

public class ThreadLocal<T> extends Object {...}

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

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

public class ThreadLocal<T> {/** ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.* No operations are exported outside of the ThreadLocal class. ...*/static class ThreadLocalMap {private Entry[] table;// a HashMap similar implemetation}//...}

Класс ThreadLocal имеет следующие методы для ввода и получения значений:

  • get(): возвращает значение локальной переменной потока в копии текущего потока.
  • initialValue(): возвращает «начальное значение» текущего потока для этой локальной переменной потока.
  • remove(): удаляет значение текущего потока для этой локальной переменной потока.
  • set(T value): устанавливает для текущей копии потока локальной переменной указанного значения.

3. Как использовать ThreadLocal?

В примере ниже используются две локальные переменные потока, т. е. threadId и startDate. Обе переменные определены как «частные статические» поля, как и рекомендуется. «ThreadId» будет использоваться для идентификации потока, который в данный момент выполняется, а «startDate» будет использоваться для получения времени начала выполнения потока.

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

class DemoTask implements Runnable {// Atomic integer containing the next thread ID to be assignedprivate static final AtomicInteger nextId = new AtomicInteger(0);// Thread local variable containing each thread's IDprivate static final ThreadLocal<Integer> threadId= new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue(){return nextId.getAndIncrement();}};// Returns the current thread's unique ID, assigning it if necessarypublic int getThreadId() {return threadId.get();}// Returns the current thread's starting timestampprivate static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {protected Date initialValue() {return new Date();}};@Overridepublic void run() {System.out.printf("Starting Thread: %s : %s\n", getThreadId(), startDate.get());try {TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));} catch(InterruptedException e) {e.printStackTrace();}System.out.printf("Thread Finished: %s : %s\n", getThreadId(), startDate.get());}}

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

Starting Thread: 0 : Wed Dec 24 15:04:40 IST 2014Thread Finished: 0 : Wed Dec 24 15:04:40 IST 2014Starting Thread: 1 : Wed Dec 24 15:04:42 IST 2014Thread Finished: 1 : Wed Dec 24 15:04:42 IST 2014Starting Thread: 2 : Wed Dec 24 15:04:44 IST 2014Thread Finished: 2 : Wed Dec 24 15:04:44 IST 2014

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

Наиболее распространенное использование thread local — когда у вас есть некоторый объект, который не является потокобезопасным, но вы хотите избежать синхронизации доступа к этому объекту с помощью ключевого слова/блока synchronized. Вместо этого дайте каждому потоку свой собственный экземпляр объекта для работы.

Хорошей альтернативой синхронизации или threadlocal является создание локальной переменной. Локальные переменные всегда потокобезопасны. Единственное, что может помешать вам сделать это, — это ограничения дизайна вашего приложения.

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

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

Прокрутить вверх