Одной из редко известных функций среди разработчиков является локальное хранилище потоков. Идея проста, и необходимость в ней возникает в сценариях, где нам нужны данные, которые… хорошо локальны для потока. Если у нас есть два потока, мы ссылаемся на одну и ту же глобальную переменную, но мы хотим, чтобы они имели отдельное значение, независимо инициализированное друг от друга.
Большинство основных языков программирования имеют реализацию этой концепции. Например, в 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 являются локальными для потока, они очень глобальны в вашем коде. Убедитесь, что вам действительно нужен этот объем потока, прежде чем использовать его.
Больше информации по теме
- 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
Ссылка: | Поток локального хранилища на Java от нашего партнера JCG Mite Mitreski в блоге Java Advent Calendar . |