🏅Автор - full-stack инженер, только что закончивший обучение.Написанные технические статьи в основном составлены из заметок в процессе обучения.После прочтения,если вам понравилось,можете поставить братишке палец вверх.
🏅 У брата-исключения также есть группа по обмену программистами, приглашаем всех прийти и порыбачить.Нажмите, чтобы присоединиться к группе
Что такое ThreadLocal
ThreadLocal также называется локальной переменной потока, полное имя — локальная переменная потока.Его повод использовать в основном для решения проблемы несогласованности, вызванной параллелизмом данных в многопоточности. ThreadLocal предоставляет копию переменной для каждого потока, чтобы каждый поток не обращался к одному и тому же объекту в определенное время, что изолирует совместное использование данных несколькими потоками, а результатом является не что иное, как потребление памяти. значительно снижает потребление производительности, вызванное синхронизацией потоков, а также снижает сложность управления параллельным выполнением потоков.
В общем: ThreadLocal подходит для сценариев, где каждому потоку нужен свой независимый экземпляр, и экземпляр нужно использовать в нескольких методах, то есть переменные изолированы между потоками, но совместно используются в методах или классах.
Так в чем же разница между ThreadLocal и Synchronized?
Хотя и ThreadLocal, и Synchonized используются для одновременного доступа нескольких потоков, между ними все же есть некоторые существенные различия:
Синхронизированный — это механизм, который использует блокировки, так что переменные или блоки кода могут быть доступны только одному потоку за раз. ThreadLocal предоставляет копию переменной для каждого потока, чтобы каждый поток не обращался к одному и тому же объекту в определенное время, что изолирует совместное использование данных несколькими потоками. несколько потоков для обмена данными.
Примечание. ThreadLocal не может использовать атомарные типы, только типы объектов.
Простое использование ThreadLocal
public class ThreadLocaTest {
private static ThreadLocal<String> local = new ThreadLocal<String>();
static void print(String str) {
//打印当前线程中本地内存中变量的值
System.out.println(str + " :" + local.get());
//清除内存中的本地变量
localVar.remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
ThreadLocaTest.local.set("xdclass_A");
print("A");
//打印本地变量
System.out.println("清除后:" + local.get());
}
},"A").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
ThreadLocaTest.local.set("xdclass_B");
print("B");
System.out.println("清除后 " + localVar.get());
}
},"B").start();
}
}
После запуска вы можете увидеть, что значение xdclass_A равно null, и значение xdclass_B также равно null, что указывает на то, что оба потока получили переменные, хранящиеся в их собственных потоках, и переменные, полученные между ними, не будут перепутаны.
Сценарий введения основного приложения ThreadLocal
Роль ThreaLocal в каждом потоке требует сохранения информации независимо, что удобно для других методов того же потока для получения информации.Поскольку информация, полученная каждым потоком, может быть разной, метод, выполненный ранее, после сохранения информации можно напрямую получить через ThreadLocal, избегая необходимости передавать параметры, что аналогично концепции глобальных переменных. Например, передача информации после расшифровки токена входа пользователя, информации о правах пользователя и имени пользователя, полученного из пользовательской системы.
Как показано на рисунке выше, например, метод 1 потока A создает переменную A, а метод 2 находится в том же потоке, что и метод 1, поэтому созданная переменная A является общей.
#用户微服务配置token解密信息传递例子
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
LoginUser loginUser = new LoginUser();
loginUser.setId(id);
loginUser.setName(name);
loginUser.setMail(mail);
loginUser.setHeadImg(headImg);
threadLocal.set(loginUser);
后续想直接获取到直接threadLocal.getxxx就可以了
Как использовать ThreadLocal для решения проблемы потокобезопасности
В нашем обычном SpringWeb-проекте мы обычно делим бизнес на Controller, Service, Dao и т.д. Также мы знаем, что аннотация @Autowired по умолчанию использует singleton-режим. Вы когда-нибудь думали, что когда приходят разные потоки запросов, поскольку уровень Dao использует синглтон, есть только одно соединение, отвечающее за подключение к базе данных, В это время, если все запрашивающие потоки подключатся к базе данных, это вызовет это Как Spring решает проблему небезопасности потоков?
Поток Connection, собранный на уровне Dao, определенно безопасен, и решение заключается в использовании метода ThreadLocal. Когда каждый поток запросов использует Connection, он получает его из ThreadLocal один раз. Если значение равно null, это означает, что нет соединения с базой данных, и после соединения оно будет сохранено в ThreadLocal. Таким образом, каждый поток сохраняет a Копия вашего собственного Connection. Каждый поток поддерживает свои собственные данные для обеспечения изоляции потоков.
Сценарии, когда ThreadLocal используется с осторожностью
Первый момент (потоки в пуле потоков вызывают ThreadLocal): поскольку управление потоками в пуле потоков является методом повторного использования потоков, потоки очень трудно заканчиваются в пуле потоков, и более вероятно, что они никогда не заканчивается. Это означает, что продолжительность потока непредсказуема и даже соответствует времени жизни JVM.
Второй момент (в асинхронной программе): передача параметра ThreadLocal ненадежна, так как после того, как поток отправит запрос, он не будет продолжать работать в ожидании удаленного результата возврата, после получения реального результата возврата он может быть другой поток обрабатывает.
Третий момент: После использования ThreadLocal рекомендуется вызывать метод remove(), что предотвратит возникновение переполнения памяти, т.к. ThreadLocal является слабой ссылкой. Если на ThreadLocal нет сильной внешней ссылки, он будет очищен во время сборки мусора.
Легко освоить базовую интерпретацию исходного кода ThreadLocal + принцип
Метод set ThreadLocal, first Thread t = Thread.currentThread означает получение текущего потока, затем получение атрибута ThreadLocalMap в потоке, а затем оценка ThreadLocalMap, если она не пуста, напрямую обновить значение переменной для сохранения. , в противном случае создайте threadLocalMap и назначьте его.
Так что же делает метод ThreadLocalMap? Далее, давайте посмотрим.Видно, что ThreadLocalMap является внутренним статическим классом ThreadLocal.Композиция этого класса в основном использует Entry для сохранения данных, а также это унаследованная слабая ссылка. Используйте ThreadLocal в качестве ключа внутри записи, где мы будем использовать значение, которое мы сами установили в качестве значения.
Поговорив выше о методах set и ThreadLocalMap, давайте взглянем на метод get. Видно, что метод get очень похож на метод set, он также сначала получает текущий поток, затем получает ThreadLocalMap потока, а затем оценивает карту. Если данные карты пусты, получите сохраненное значение. Если данные равны нулю, начинается инициализация, и в результате инициализации значение, хранящееся в Theradlocalmap, равно нулю.
Видно, что в основном все операции имеют этот ThreadLocalMap.Этот класс не реализует интерфейс карты, это обычный класс java, но реализованный класс аналогичен функции карты.Данные хранятся в Entry, который наследует из WeakReference и использует сохраненные пары ключ-значение, а ключ является ссылкой на ThreadLocal. Каждый поток имеет объект ThreadLocalMap, и каждый новый поток Thread создает экземпляр ThreadLocalMap и присваивает значение переменной-члену threadLocals.
[Вопрос интервью] Почему ключ ThreadLocal является слабой ссылкой, в чем проблема, если это сильная ссылка?
Что такое слабая ссылка? (Сяобай, пожалуйста, смотрите, старший брат, пожалуйста, пропустите~)
В java, за исключением базовых типов данных, все остальные типы являются ссылочными типами, а java делит ссылочные типы на сильные ссылки, мягкие ссылки, слабые ссылки и виртуальные ссылки в соответствии с продолжительностью жизненного цикла. В обычных обстоятельствах мы в основном применяем только тип сильной ссылки, а другие типы ссылок можно увидеть только на собеседовании или при чтении исходного кода.
Сильная ссылка: такой объект, как новый, является сильной ссылкой Object obj = new Object()
Мягкие ссылки имеют более короткий жизненный цикл, чем сильные ссылки, которые реализованы через класс SoftReference.Когда в памяти достаточно места, сборщик мусора не будет ее освобождать, потому что, когда JVM считает, что места в памяти недостаточно, она попытается восстановить объект, указанный в мягкой ссылке, что означает, что JVM очистит объект мягкой ссылки, прежде чем выдать исключение OutOfMemoryError.
Сценарий использования soft reference: Больше подходит для реализации кэша.При достаточном объеме памяти кэш хранится в памяти.Если памяти недостаточно, кэш можно восстановить.
Слабая ссылка: Слабая ссылка реализована через класс WeakReference.Его жизненный цикл короче, чем у мягких ссылок (одна короче другой).При сборке мусора объект будет утилизирован независимо от того, достаточно ли места в памяти или нет.
Сценарий использования: если объект используется только изредка, есть надежда, что его можно будет получить в любое время, когда он используется, но я не хочу влиять на сборку мусора объекта.В настоящее время мы можем рассмотреть возможность использования слабая ссылка, указывающая на этот объект.
После стольких разговоров я все еще не говорил об этом вопросе интервью Теперь давайте поговорим о том, как ответить на этот вопрос интервью.
Почему ThreadLocal WeakReference?
Во-первых, если это строгая ссылка, даже если значение ThreadLocal равно null, ThreadLocalMap все равно будет иметь состояние строгой ссылки ThreadLocal.Если ее не удалить вручную, ThreadLocal не будет переработан, что приведет к утечке памяти Entry.
Во-вторых, если это слабая ссылка, объект, ссылающийся на ThreadLocal, повторно используется, а ThreadLocalMap по-прежнему сохраняет слабую ссылку ThreadLocal, даже если она не удалена вручную, ThreadLocal будет повторно использован. Значение будет очищено в следующий раз, когда ThreadLocalMap вызовет метод set/get/remove.