Привет всем, я Сяо Цай, Сяо Цай, который хочет быть Цай Буцаем в интернет-индустрии. Она может быть мягкой или жесткой, как она мягкая, а белая проституция жесткая!Черт~ Не забудьте поставить мне тройку после прочтения!
Эта статья в основном знакомит
ThreadLocal 的使用
При необходимости вы можете обратиться к
Если это поможет, не забудьтекак❥
Официальный аккаунт WeChat открыт,Хорошая еда, студенты, которые не обратили внимания, не забудьте обратить внимание!
Ранее мы упоминали в серии параллелизмаThreadLocal
Классы и основные методы использования, тогда давайте посмотримThreadLocal
Как именно он используется!
Введение в ThreadLocal
концепция
ThreadLocalКлассы используются для предоставления локальных переменных в потоке. Доступ к таким переменным осуществляется в многопоточной среде (getиsetдоступ к методу), чтобы гарантировать, что переменные каждого потока относительно независимы от переменных в других потоках.ThreadLocalПримеры обычноprivate staticтип, используемый для связывания потоков и контекстов.
эффект
- передавать данные
Предоставляет локальные переменные внутри потока. в состоянии пройтиThreadLocalПередавайте общие переменные в разные компоненты в одном потоке.
- параллелизм потоков
Он подходит для многопоточных параллельных ситуаций.
- изоляция резьбы
Переменные каждого потока независимы и не влияют друг на друга.
ThreadLocal в действии
1. Общие методы
ThreadLocal ()
Конструктор для создания объекта ThreadLocal
void set (T value)
Установить локальные переменные, привязанные к текущему потоку
T get ()
Получить локальные переменные, привязанные к текущему потоку
void remove ()
Удаляет локальные переменные, привязанные к текущему потоку
2. Зачем использовать ThreadLocal
Во-первых, давайте рассмотрим набор сценариев кода в параллельных условиях:
@Data
public class ThreadLocalTest {
private String name;
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() +
"\t 拿到数据:" + tmp.getName());
});
thread.setName("Thread-" + i);
thread.start();
}
}
}
Наш идеальный вывод кода должен выглядеть так:
/** OUTPUT **/
Thread-0 拿到数据:Thread-0
Thread-1 拿到数据:Thread-1
Thread-2 拿到数据:Thread-2
Thread-3 拿到数据:Thread-3
Но фактический вывод таков:
/** OUTPUT **/
Thread-0 拿到数据:Thread-1
Thread-3 拿到数据:Thread-3
Thread-1 拿到数据:Thread-1
Thread-2 拿到数据:Thread-2
Неважно, если порядок не в порядке, но мы можем видетьThread-0Значение, которое получает этот поток, равноThread-1
Из результатов видно, что когда несколько потоков обращаются к одной и той же переменной, возникает исключение, потому что данные между потоками не изолированы!
Параллельные потоки вопросы возникают? От этого замка не уйти! Это аккуратно и быстро, когда вы написали следующий код:
@Data
public class ThreadLocalTest {
private String name;
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
synchronized (tmp) {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()
+ "\t" + tmp.getName());
}
});
thread.setName("Thread-" + i);
thread.start();
}
}
}
/** OUTPUT **/
Thread-2 Thread-2
Thread-3 Thread-3
Thread-1 Thread-1
Thread-0 Thread-0
Судя по результатам, блокировка решает вышеуказанные проблемы, ноsynchronizedЧасто используется для проблем с многопоточным обменом данными, а не для проблем с изоляцией многопоточных данных. использовать здесьsynchronizedХотя проблема и решена, но несколько неуместна, иsynchronizedЭто тяжеловесная блокировка, и она добавлена опрометчиво, чтобы добиться многопоточной изоляции данных.synchronized, также влияет на производительность.
Метод блокировки также был отклонен, так как это решить? лучше использоватьThreadLocalПопробуйте ножом:
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public String getName() {
return threadLocal.get();
}
public void setName(String name) {
threadLocal.set(name);
}
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() +
"\t 拿到数据:" + tmp.getName());
});
thread.setName("Thread-" + i);
thread.start();
}
}
}
Прежде чем смотреть на вывод, давайте посмотрим на изменения в коде.
Сначала еще одинprivate static
декоративныйThreadLocal, затем вsetName
, мы на самом деле идем кThreadLocalв нем хранятся данныеgetName
когда мы былиThreadLocalПолучить данные внутри. Это кажется очень простым в эксплуатации, но действительно ли это обеспечивает изоляцию данных между потоками?Давайте посмотрим на результаты:
/** OUTPUT **/
Thread-1 拿到数据:Thread-1
Thread-2 拿到数据:Thread-2
Thread-0 拿到数据:Thread-0
Thread-3 拿到数据:Thread-3
Из результатов видно, что каждый поток может получить соответствующие данные.ThreadLocalТакже была решена проблема изоляции данных между несколькими потоками.
Итак, подведем итоги,Зачем нужно использовать ThreadLocal и в чем разница с синхронизированным
- synchronized
принцип:Механизм синхронизации использует метод «время за пространство», который предоставляет только переменную для разных потоков, которые ставятся в очередь для доступа.
Фокус:Синхронизированный доступ к ресурсам между несколькими потоками
- ThreadLocal
принцип:ThreadLocal использует подход «пространство во времени», предоставляя каждому потоку копию переменной, обеспечивая одновременный доступ, не мешая друг другу.
Фокус:В многопоточности данные между каждым потоком изолированы друг от друга.
3. Внутренняя структура
Из приведенного выше случая мы видим, чтоThreadLocalДва основных методаset()
иget()
Тогда мы могли бы также догадаться, если давайте спроектируемThreadLocal, как мы проектируем, есть такая идея: каждыйThreadLocalоба создаютMap, а затем используйте поток какMapизkey, локальная переменная, которая будет храниться какMapизvalue, чтобы можно было добиться эффекта изоляции локальных переменных каждого потока.
Эта мысль также верна, раноThreadLocalразработан таким образом, но вJDK 8Затем дизайн был изменен следующим образом:
процесс проектирования:
-
каждыйThreadВнутри нити имеетсяThreadLocalMap
-
ThreadLocalMapХранится вThreadLocalобъектkey, переменная потокаvalue
-
ThreadВнутреннийMapОтThreadLocalподдерживаетсяThreadLocalответственный заMapУстановка и получение значений переменных потока
-
Для разных потоков каждый раз при получении значения копии другие потоки не могут получить значение копии потока, что будет формировать изоляцию копий, не мешая друг другу.
Примечание:У каждой темы должен быть свойmap, но этот класс является обычнымJavaкласс, не реализуетMapинтерфейс, но с чем-то вродеMapаналогичная функция.
Реализация этого кажется более сложной, чем мы думали ранее. Каковы преимущества этого?
- каждыйMapхранитсяEntryЧисло будет меньше, потому что предыдущий объем хранилища изменяется наThreadопределяется количествомThreadMapКоличество решений, находящихся в фактической разработке,ThreadLocalменьше чемThreadколичество.
- когдаThreadПосле разрушения соответствующиеThreadLocalMapОн также будет уничтожен, что может уменьшить использование памяти.
4. Анализ исходного кода
Сначала посмотримThreadLocalMapКакие члены:
если ты виделHashMapИсходный код , вы определенно почувствуете, что они очень знакомы, среди которых:
- INITIAL_CAPACITY: начальная емкость, должна быть степенью 2
- table: хранить данныеtable
- size: в массивеentriesчисло, используемое для сужденияtableПревышает ли текущее использование пороговое значение
- threshold: Порог для расширения, когда использование таблицы больше, чем она будет расширена.
ThreadLocals
ThreadКласс имеет видThreadLocal.ThreadLocalMapпеременная типаThreadLocals, это используется для сохранения личных данных каждого потока.
ThreadLocalMap
ThreadLocalMapдаThreadLocalвнутренний класс каждых данных сEntryсэкономить, из нихEntryХранится в паре ключ-значение, ключThreadLocalцитаты.
Мы видим, чтоEntryунаследовано отWeakReference, это потому, что если это сильная ссылка, даже еслиThreadLocalУстановить какnull,GCне будет переработан, потому чтоThreadLocalMapНа это есть сильная отсылка.
без ручного удаления этогоEntryа такжеCurrentThreadВ предположении, что все еще работает, всегда есть сильная цепочка ссылокthreadRef ->
currentThread ->
threadLocalMap ->
entry,Entryне перерабатывается(EntryвключеныThreadLocalэкземпляр иvalue), привести кEntryУтечка памяти.
Означает ли это, что если вы используетеслабая ссылка, не вызоветутечка памятиЧто ж, это тоже неправильно.
Потому что если бы мы не удаляли вручнуюEntryслучае, в это времяEntryсерединаkey == null, на этот раз нет сильной ссылки, указывающей наthreaLocalнапример, такthreadLocalможет быть успешноgcпереработка, ноvalueне будет переработан, и этот кусокvalueникогда не будут доступны, что приведет кутечка памяти
Далее мы смотрим наThreadLocalMapНесколько основных методов:
установить метод
Во-первых, давайте посмотрим на исходный код:
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 如果当前线程不存在ThreadLocalMap对象则调用createMap进行ThreadLocalMap对象的初始化
// 并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Процесс реализации:
-
Сначала получите текущий поток и получите его на основе текущего потокаmap
-
если полученоmapне пусто, установите для параметра значениеmapСредний (в настоящее времяThreadLocalцитаты какkey)
-
еслиMapЕсли он пуст, создайте его для этого потокаmap, и установите начальное значение
получить метод
Исходный код выглядит следующим образом:
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
@SuppressWarnings("unchecked")
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 如果当前线程不存在ThreadLocalMap对象则调用createMap进行ThreadLocalMap对象的初始化
// 并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
// 返回设置的值value
return value;
}
Процесс реализации:
- Сначала получите текущий поток, получите один в соответствии с текущим потокомmap
- если полученоmapне пусто, тоmapКитай и ИзраильThreadLocalцитаты какkeyПодойди сюдаmapполучить соответствующийEntry entry, иначе перейти кчетвертый шаг
- еслиEntry entryне пусто, вернутьсяentry.value, иначе перейти кчетвертый шаг
- mapпустой илиentryпусто, проходитеinitialValueфункция для получения начального значенияvalue, затем используйтеThreadLocalцитаты иvalueв видеfirstKeyиfirstValueсоздать новыйmap
метод удаления
Исходный код выглядит следующим образом:
public void remove() {
// 获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null)
// 存在则调用map.remove
m.remove(this);
}
// 以当前ThreadLocal为key删除对应的实体entry
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
Процесс реализации:
- Сначала получите текущий поток и получите его на основе текущего потокаmap
- если полученоmapне пустой, удалите текущийThreadLocalобъект, соответствующийentry
метод начального значения
Исходный код выглядит следующим образом:
protected T initialValue() {
return null;
}
В исходном коде мы видим, что этот метод просто возвращаетnull, этот метод сначала передается в потокеget ()метод для доступа к потокуThreadLocalвызывается, только если поток вызывает его первымset ()метод не будет вызыватьсяinitialValue ()обычно этот метод вызывается не более одного раза.
если мы хотимThreadLocalЛокальные переменные потока имеют исключениеnullкроме начального значения, вы должны передать подкласснаследовать ThreadLocalчтобы переопределить этот метод, который может быть реализован анонимным внутренним классом.
【КОНЕЦ】
этоThreadLocalЭто все для введения, я надеюсь, что маленькие друзья, которые читают это здесь, могут что-то получить.
Дорога длинная, Сяокай будет искать ее вместе с вами!
Если вы будете усердно работать сегодня, завтра вы сможете сказать на одну вещь меньше, чтобы попросить о помощи!
Я Сяо Цай, человек, который учится у вас.
💋
Официальный аккаунт WeChat открыт,Хорошая еда, студенты, которые не обратили внимания, не забудьте обратить внимание!