Чому змінні Java ThreadLocal повинні бути статичними


101

Я читав тут JavaDoc для Threadlocal

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

і він говорить: "Елементи ThreadLocal - це типово приватні статичні поля в класах, які хочуть асоціювати стан з потоком (наприклад, ідентифікатор користувача або ідентифікатор транзакції)."

Але моє запитання: чому вони вирішили зробити його статичним (як правило) - це робить дещо заплутаним стан "за ниткою", але поля статичні?

Відповіді:


131

Тому що якби це поле рівня екземпляра, то насправді це було б "На нитку - за інстанцію", а не просто гарантоване "За нитку". Це, як правило, не те семантичне, яке ви шукаєте.

Зазвичай він містить щось на зразок об'єктів, які відносяться до розмови користувача, веб-запиту тощо. Ви також не хочете, щоб вони підрозділялися на екземпляр класу.
Один веб-запит => один сеанс збереження.
Не один веб-запит => один постійний сеанс на об'єкт.


2
Мені подобається це пояснення, оскільки воно показує, як слід використовувати
ThreadLocal

4
Per-thread-per-instance може бути корисною семантикою, але більшість застосувань для цього шаблону буде включати стільки об'єктів, що було б краще використовувати a, ThreadLocalщоб утримувати посилання на хеш-набір, який відображає об’єкти на екземпляри на один потік.
supercat

@optional Це просто означає, що кожен екземпляр нестатичного ThreadLocalповинен містити свої власні локальні дані, навіть якщо ці ThreadLocalекземпляри існують в одному потоці. Це не обов'язково неправильно робити це - я думаю, що це може бути найменш популярним малюнком із двох
гег

17

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



3

Причина полягає в тому, що до змінних звертається через вказівник, пов'язаний з потоком. Вони діють як глобальні змінні з ниткою області, отже, статична є найбільш підходящою. Це те, як ви отримуєте нитку локального стану в таких речах, як pthreads, тому це може бути просто випадковістю історії та впровадження.


1

Використання для потоку локальних точок на екземпляр для потоку - це якщо ви хочете, щоб щось було видно у всіх методах об'єкта і було це безпечно для потоку, не синхронізуючи доступ до нього, як це було б у звичайному полі.


1

Зверніться до цього , це дасть краще розуміння.

Коротше кажучи, ThreadLocalоб’єкт працює як карта ключових значень. Коли ThreadLocal get/setметод виклику потоку , він буде витягувати / зберігати об'єкт потоку в ключі карти, а значення - у значенні карти. Ось чому різні потоки мають різну копіювану цінність (яку потрібно зберігати локально), оскільки вона знаходиться в різних картах.

Ось чому вам потрібна лише одна карта, щоб зберегти всі значення. Хоча це і не потрібно, ви можете мати декілька карт (без оголошення статичного), щоб зберегти також кожен об'єкт потоку, що, абсолютно непотрібно, саме тому статична змінна є кращою.


-1

static final ThreadLocal змінні є безпечними для потоків.

staticробить змінну ThreadLocal доступною для кількох класів лише для відповідного потоку. це своєрідна декарація глобальної змінної відповідних локальних змінних потоків у кількох класах.

Ми можемо перевірити безпеку цього потоку за допомогою наступного зразка коду.

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

.

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"

Висновок

Паралельні потоки можуть отримати правильні користувальницькі користувачі, навіть якщо він оголошений як "статичний кінцевий ThreadLocal"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.