ThreadLocal — одна из наиболее часто используемых технологий в разработке и важная тестовая площадка для интервью. В этой статье будут представлены использование, принцип реализации, проблема утечки памяти и сценарии использования ThreadLocal от мелкого к более глубокому.
Роль ThreadLocal
Часто используется такое требование в одновременном программировании: каждая нить должна получить доступ к переменной с тем же именем, но значение переменной в каждом потоке отличается.
Если бы это были вы, как бы вы реализовали вышеуказанную функциональность? Общая идея такова:
поделился с помощью тредаMap<Thread,Object>
, ключ в карте — это объект потока, а значение — это значение, которое нужно сохранить. Тогда нам просто нужно пройтиmap.get(Thread.currentThread())
Вы можете получить значение переменной в этом потоке.
Этот способ действительно может удовлетворить наши потребности, но каковы недостатки? ——Ответ: требуется синхронизация, а эффективность низкая!
Поскольку этот объект карты должен совместно использоваться всеми потоками, он должен быть заблокирован для обеспечения безопасности потоков. Конечно, мы можем использоватьjava.util.concurrent.*
в упаковкеConcurrentHashMap
Повысьте эффективность параллелизма, но этот метод может только уменьшить детализацию блокировок и не может принципиально избежать блокировок синхронизации. в то время как JDK предоставляетThreadLocal
может хорошо решить эту проблему. Давайте посмотрим, как ThreadLocal эффективно реализует это требование.
Как использовать ThreadLocal
Прежде чем представить принцип ThreadLocal, сначала кратко представим его использование.
public class Main{
private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public void start() {
for (int i=0; i<10; i++) {
new Thread(new Runnable(){
@override
public void run(){
threadLocal.set(i);
threadLocal.get();
threadLocal.remove();
}
}).start();
}
}
}
- Во-первых, нам нужно создать объект ThreadLocal, разделяемый потоками, который используется для хранения значений типа Integer;
- Затем в каждом потоке можно работать через Threadlocal следующим методом:
-
set(obj)
: сохранить данные в текущем потоке -
get()
: получить данные в текущем потоке -
remove()
: удалить данные в текущем потоке
-
Использование ThreadLocal очень простое, ключ заключается в принципе реализации, стоящем за ним. Вернемся к вопросу выше: как именно ThreadLocal избегает блокировок синхронизации, чтобы обеспечить эффективное чтение и запись?
Принцип реализации ThreadLocal
Внутренняя структура ThreadLocal, как показано ниже:
ThreadLocal
не поддерживаетThreadLocalMap
, не является контейнером для хранения данных, это всего лишь набор инструментов, предоставляющий методы для работы с контейнером, такие как получение, установка, удаление и т.д. иThreadLocal
внутренний классThreadLocalMap
это контейнер, в котором хранятся данные, а контейнерThread
поддерживать.
КаждыйThread
Объекты содержат А.ThreadLocalMap
переменная-член типаthreadLocals
, в котором хранятся все объекты ThreadLocal в этом потоке и их соответствующие значения.
ThreadLocalMap
по одномуEntry
композиция объекта,Entry
Код выглядит следующим образом:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry
унаследовано отWeakReference<ThreadLocal<?>>
,ОдинEntry
Зависит отThreadLocal
объект иObject
составляют. Отсюда видно, чтоEntry
Ключ представляет собой объект ThreadLocal и является слабой ссылкой. Когда нет строгой ссылки на ключ, ключ будет утилизирован сборщиком мусора.
Итак, как же работает ThreadLocal? Давайте посмотрим на методы set и get.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Когда метод set выполняется, ThreadLocal сначала получает объект текущего потока, а затем получает объект ThreadLocalMap текущего потока. Затем используйте текущий объект ThreadLocal в качестве ключа и сохраните значение в объекте ThreadLocalMap.
Процесс выполнения метода get аналогичен. ThreadLocal сначала получит объект текущего потока, а затем получит объект ThreadLocalMap текущего потока. Затем используйте текущий объект ThreadLocal в качестве ключа для получения соответствующего значения.
Так как каждый поток содержит свой собственныйчастныйThreadLocalMap, эти контейнеры независимы друг от друга и не влияют друг на друга, поэтому нет проблемы безопасности потоков, поэтому нет необходимости использовать механизм синхронизации для обеспечения взаимного исключения нескольких потоков, обращающихся к контейнеру.
Зачем использовать слабые ссылки?
Студенты, не знакомые со слабыми ссылками, могут обратиться к другой статье автора: http://blog.csdn.net/u010425776/article/details/50760053.
Одним из основных принципов дизайна Java в начале было ослабление указателей. Разработчики Java надеются упростить программирование за счет разумного дизайна, чтобы программистам не приходилось иметь дело со сложными операциями с указателями. Однако указатели существуют объективно, и «операции с указателями» неизбежно связаны с текущей разработкой Java. как:
Object a = new Object();
Приведенный выше код создает сильную ссылку а. Пока существует сильная ссылка, сборщик мусора не вернет объект. Если объект очень большой, для экономии места в памяти нам нужно вручную удалить сильную ссылку после использования объекта, как показано в следующем коде:
a = null;
На этом этапе сильная ссылка на объект удаляется, и сборщик мусора может вернуть объект. Но в процессе программисту все же приходится иметь дело с указателями. Чтобы ослабить концепцию указателей, появляются слабые ссылки.Следующий код создает слабую ссылку типа Person:
WeakReference<Person> wr = new WeakReference<Person>(new Person());
На этом этапе программисту больше не нужно обращать внимание на указатель: пока нет строгой ссылки на объект Person, сборщик мусора будет автоматически освобождать объект при каждом запуске.
Так вот почему ключи в ThreadLocalMap используют слабые ссылки. Когда объект ThreadLocal в потоке израсходован и нет строгой ссылки, указывающей на него, сборщик мусора автоматически перезапустит ключ для экономии памяти.
Ну, вот и проблема - это приводит к проблеме с утечкой памяти!
Проблема с утечкой памяти ThreadLocal
В ThreadLocalMap только ключ является слабой ссылкой, а значение по-прежнему является сильной ссылкой. Когда ThreadLocal в потоке израсходован и нет строгой ссылки, указывающей на него, объект, на который указывает ключ, будет утилизирован сборщиком мусора, а ключ станет нулевым; однако в это время объект, указанный to по значению и по значению Между ними по-прежнему существует сильная ссылочная связь.Пока эта связь не разрешена, объект, на который указывает значение, никогда не будет утилизирован сборщиком мусора, что приведет к утечке памяти!
Но не волнуйтесь, ThreadLocal предлагает решение этой проблемы.
Каждый раз, когда выполняются операции установки, получения и удаления, ThreadLocal будет удалять запись, ключ которой равен нулю, чтобы избежать утечек памяти.
Затем проблема возникает снова: если поток выполняется в течение длительного периода времени, а методы set, get и remove больше не вызываются после помещения большого объекта в LocalThreadMap, это все еще может вызывать утечки памяти.
Эта проблема действительно существует, через Threadlocal решить ее невозможно, но программисту необходимо разработать транзакцию Remove при использовании Threadlocal, чтобы избежать утечки памяти.
Используйте сцену Threadlocal
Хранение сеанса веб-системы является типичным сценарием приложения ThreadLocal.
Веб-контейнер использует многопоточную модель изоляции потоков, то есть каждый запрос соответствует потоку, а потоки изолированы друг от друга и не обмениваются данными. Это упрощает модель программирования, и программисты могут разрабатывать такие многопоточные приложения с однопоточным мышлением.
Когда приходит запрос, текущая информация о сеансе может быть сохранена в ThreadLocal, и информация о сеансе может быть использована в любой момент в процессе обработки запроса, и информация о сеансе между каждым запросом не влияет друг на друга. Когда обработка запроса завершена, информация о текущем сеансе может быть удалена с помощью метода удаления.