Почему переменные Java ThreadLocal должны быть статическими

102

Я читал здесь JavaDoc для Threadlocal

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

и говорится: «Экземпляры ThreadLocal обычно представляют собой частные статические поля в классах, которые хотят связать состояние с потоком (например, ID пользователя или ID транзакции)».

Но у меня вопрос: почему они решили сделать его статическим (как правило) - это немного сбивает с толку наличие состояния «на поток», но поля статические?

Kellyfj
источник

Ответы:

132

Потому что, если бы это было поле уровня экземпляра, тогда это было бы «на поток - на экземпляр», а не просто гарантированное «на поток». Обычно это не та семантика, которую вы ищете.

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

Аффе
источник
2
Мне нравится это объяснение, потому что оно показывает, как следует использовать ThreadLocal
kellyfj
4
Поток-на-экземпляр может быть полезной семантикой, но в большинстве случаев использование этого шаблона будет включать в себя так много объектов, что было бы лучше использовать a ThreadLocalдля хранения ссылки на хэш-набор, который сопоставляет объекты с экземплярами потока.
supercat
@optional Это просто означает, что каждый экземпляр нестатического ThreadLocalбудет содержать свои собственные локальные данные потока, даже если эти ThreadLocalэкземпляры существуют в одном потоке. Это не обязательно неправильно - я полагаю, что это может быть наименее популярный шаблон из двух
geg
17

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

Аднан Мемон
источник
12

Так не должно быть. Важно то, что это должен быть синглтон.

бесспорный
источник
3

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

Укко
источник
1

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

Крис Мавата
источник
1

Обратитесь к этому , это даст лучшее понимание.

Короче говоря, ThreadLocalобъект работает как карта "ключ-значение". Когда поток вызывает ThreadLocal get/setметод, он извлекает / сохраняет объект потока в ключе карты и значение в значении карты. Вот почему разные потоки имеют разное скопированное значение (которое вы хотите сохранить локально), потому что оно находится в другой записи карты.

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

GMsoF
источник
-1

static final ThreadLocal переменные потокобезопасны.

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

Мы можем проверить безопасность этого потока с помощью следующего примера кода.

  • CurrentUser - сохраняет текущий идентификатор пользователя в ThreadLocal
  • TestService- Простой сервис с методом - getUser()получить текущего пользователя из CurrentUser.
  • TestThread - этот класс используется для создания нескольких потоков и одновременной установки идентификаторов пользователей

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Запустите основной класс TestThread. Вывод -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Резюме анализа

  1. «основной» поток запускается и устанавливает текущего пользователя как «62», параллельно запускается поток «ForkJoinPool.commonPool-worker-2» и устанавливает текущего пользователя как «31», параллельно запускается поток «ForkJoinPool.commonPool-worker-3» и устанавливает текущий пользователь как "81", параллельно запускается поток "ForkJoinPool.commonPool-worker-1" и устанавливает текущего пользователя как "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. Все эти вышеупомянутые потоки будут спать в течение 3 секунд
  3. mainвыполнение завершается и вывод текущего пользователя как «62», параллельное ForkJoinPool.commonPool-worker-1выполнение завершается и вывод текущего пользователя как «87», параллельное ForkJoinPool.commonPool-worker-2выполнение завершается и вывод текущего пользователя как «31», параллельное ForkJoinPool.commonPool-worker-3выполнение завершается и вывод текущего пользователя как «81»

Вывод

Параллельные потоки могут получать правильные идентификаторы пользователей, даже если они объявлены как "static final ThreadLocal"

шиджин радж
источник