Этот пост является частью 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://software.intel.com/en-us/articles/use-thread-local-storage-to-reduce-synchronization