Статьи

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

Этот пост является частью  Java Advent Calendar ,  и распространяется под лицензией  3.0 Attribution Commons Креативных  лицензий. Если вам это нравится, пожалуйста, распространите информацию, поделившись, чирикать, FB, G + и т. Д.!      Он также был переиздан по  адресу https://www.voxxed.com/blog/2014/12/thread-local-storage-in-java/

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

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

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

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

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();
  }

}

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

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  : 

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 являются локальными для потока, они очень глобальны в вашем коде. Так что, если вы хотите избежать того, чтобы разработчики сопровождения преследовали вас и мстили, убедитесь, что вам действительно нужна эта область потока, прежде чем использовать ее. 

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

http://en.wikipedia.org/wiki/Thread-local_storage

http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage/

https://plumbr.eu/blog/how-to-shoot-yourself-in-foot-with-threadlocals

http://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable

https://plumbr.eu/blog/when-and-how-to-use-a-threadlocal

https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide

https://software.intel.com/en-us/articles/use-thread-local-storage-to-reduce-synchronization