Статьи

Поток локального хранилища в Java

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

Большинство основных языков программирования имеют реализацию этой концепции. Например, в C ++ 11 есть даже ключевое слово thread_local , а Ruby выбрал подход API.

В Java также реализована концепция с java.lang.ThreadLocal <T> и его подклассом java.lang.InheritableThreadLocal <T> начиная с версии 1.2, поэтому здесь нет ничего нового и блестящего.

Допустим, по какой-то причине нам нужен специфичный для Long поток. Используя Thread local, это было бы просто:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ThreadLocalExample {
 
  public static class SomethingToRun implements Runnable {
 
    private ThreadLocal threadLocal = new ThreadLocal();
 
    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
 
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
      }
 
      threadLocal.set(System.nanoTime());
      System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
    }
  }
 
 
  public static void main(String[] args) {
    SomethingToRun sharedRunnableInstance = new SomethingToRun();
 
    Thread thread1 = new Thread(sharedRunnableInstance);
    Thread thread2 = new Thread(sharedRunnableInstance);
 
    thread1.start();
    thread2.start();
  }
 
}

Один из возможных примеров выполнения следующего кода приведет к:

1
2
3
4
5
6
7
Thread-0 null
 
Thread-0 132466384576241
 
Thread-1 null
 
Thread-1 132466394296347

В начале значение установлено равным нулю для обоих потоков, очевидно, что каждый из них работает с отдельными значениями, поскольку после установки значения System.nanoTime () в Thread-0 это не окажет никакого влияния на значение Thread-1 точно как мы и хотели, переменная длинной области потока.

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

В каких областях популярны фреймворки, использующие локальные потоки?

Spring — одна из самых популярных фреймворков в Java — использует ThreadLocals для многих частей, что легко показать простым поиском в github. Большинство использований связано с действиями или информацией текущего пользователя. На самом деле это одно из основных применений ThreadLocals в мире JavaEE, где хранится информация для текущего запроса, как в RequestContextHolder :

1
2
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
    new NamedThreadLocal<RequestAttributes>("Request attributes");

Или учетные данные пользователя текущего соединения JDBC в UserCredentialsDataSourceAdapter .

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

Распространенным примером использования этого является LocaleContextHolder, который помогает нам сохранить локаль текущего пользователя.

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

Местные потоки и утечки памяти

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


«Можете ли вы вызвать непреднамеренное удержание объекта у локальных потоков? Что вы можете. Но вы можете сделать это и с массивами. Это не означает, что локальные потоки (или массивы) — плохие вещи. Просто, что вы должны использовать их с некоторой осторожностью. Использование потоковых пулов требует особой осторожности. Небрежное использование пулов потоков в сочетании с небрежным использованием локальных потоков может привести к непреднамеренному удержанию объектов, как отмечалось во многих местах. Но возлагать вину на местных жителей неоправданно ». — Джошуа Блох

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

Еще одно неправильное использование ThreadLocal — это дизайн API. Часто я видел использование RequestContextHolder (который содержит ThreadLocal ) повсеместно, например, уровень DAO. Позже, если кто-то вызовет те же методы DAO вне запроса, как например, например, планировщик, он получит очень неприятный сюрприз.

Это создает черную магию и множество разработчиков, которые в конечном итоге выяснят, где вы живете, и посетят вас. Хотя переменные в ThreadLocal являются локальными для потока, они очень глобальны в вашем коде. Убедитесь, что вам действительно нужен этот объем потока, прежде чем использовать его.

Больше информации по теме