Публичный аккаунт WeChat:I am CR7 Если у вас есть какие-либо вопросы или предложения, пожалуйста, оставьте сообщение ниже; Последнее обновление: 2019-01-12
я спрашиваю: Прочитав этот пример, вы знаете, что делает ThreadLocal? Вы отвечаете: Не знаю, не чувствую, пример hello world вообще не вызывает интереса. ты спрашиваешь: Кто, осмелитесь привести пример производственного уровня, который реально можно использовать в работе? Я отвечаю: Да, ты "Хозяин", я сделаю все, что ты скажешь. все еще помню"Статьи об обработке запросов на анализ исходного кода Spring Cloud Netflix Zuul"RequestContext упоминается в? Это приложение производственного уровня. Каков основной принцип Zuul? Это поместить запрос в цепочку фильтров и обрабатывать его один за другим. Между фильтрами нет прямой вызывающей связи. Результаты обработки сохраняются в RequestContext и передаются, и этот RequestContext является объектом типа ThreadLocal! ! !
ты спрашиваешь: После долгого разговора, что это такое и в чем его польза?Можете ли вы дать мне представление? Я отвечаю:могу! Должен уметь! ! !
What is this
Что это такое? Это класс Java, который поддерживает дженерики.Помимо статического внутреннего класса ThreadLocalMap внутри, он на самом деле не имеет нескольких строк кода.Если вы не верите в это, вы можете убедиться в этом сами. Для чего его используют? Аннотация к классу очень понятна:
Это позволяет потокам иметь свои собственные внутренние эксклюзивные переменные.
Каждый поток может работать через методы get и set.
Вы можете переопределить метод initialValue, чтобы указать исключительное для потока значение.
Обычно он используется для изменения частных статических конечных свойств в классе, чтобы установить некоторую информацию о состоянии для потока, такую как идентификатор пользователя или идентификатор транзакции.
Каждый поток имеет слабую ссылку на экземпляр threadLocal, который не будет очищен сборщиком мусора, пока поток жив или к экземпляру threadLocal можно получить доступ.
Если вы любите задавать вопросы, у вас должны быть сомнения.Демонстрация вызывает только метод ThreadLocal.get().Как он достигает всего этого величия? Об этом автор и расскажет дальше, поехали~~~
у меня есть карта
Без лишних слов давайте посмотрим на внутреннюю реализацию метода get:
получить() исходный код
1public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13}
Логика проста:
Получить ThreadLocalMap внутри текущего потока
Если карта существует, получить значение, соответствующее текущему ThreadLocal
Карта не существует или значение не может быть найдено, затем вызовите setInitialValue для инициализации
исходный код setInitialValue()
1private T setInitialValue() { 2 T value = initialValue(); 3 Thread t = Thread.currentThread(); 4 ThreadLocalMap map = getMap(t); 5 if (map != null) 6 map.set(this, value); 7 else 8 createMap(t, value); 9 return value; 10}
Логика тоже очень проста:
Вызовите метод initialValue, чтобы получить значение инициализации [вызывающий устанавливает собственное значение инициализации, переопределяя этот метод]
Получить ThreadLocalMap внутри текущего потока
Если карта существует, добавьте текущий ThreadLocal и значение на карту.
Если карта не существует, создайте ThreadLocalMap и сохраните ее в текущем потоке.
Временная диаграмма
Для облегчения понимания автор специально нарисовал временную диаграмму, смотрите:
получить диаграмму последовательности методов
резюме
А пока можете ответить по принципу реализации ThreadLocal? Да, карта, карта под названием ThreadLocalMap, это ключ. Каждый поток имеет закрытую переменную типа ThreadLocalMap. При добавлении объекта ThreadLocal в поток он сохраняется в этой карте, поэтому потоки не будут мешать друг другу. Подводя итог, в одном предложении: у меня есть мой молодой, о нет, у меня есть моя карта. Выяснив это, вы станете намного увереннее использовать его. Однако означает ли это, что его можно смело использовать? На самом деле, не обязательно, вас ждет «большая яма».
магия удалить
«Большая яма» относится к проблеме утечек памяти, вызванных неправильным использованием ThreadLocal. Автор приводит два примера кода, чтобы проиллюстрировать эту проблему.
1public class MemoryLeak { 2 3 public static void main(String[] args) { 4 new Thread(new Runnable() { 5 @Override 6 public void run() { 7 for (int i = 0; i < 1000; i++) { 8 TestClass t = new TestClass(i); 9 t.printId(); 10 t = null; 11 } 12 } 13 }).start(); 14 } 15 16 static class TestClass{ 17 private int id; 18 private int[] arr; 19 private ThreadLocal<TestClass> threadLocal; 20 TestClass(int id){ 21 this.id = id; 22 arr = new int[1000000]; 23 threadLocal = new ThreadLocal<>(); 24 threadLocal.set(this); 25 } 26 27 public void printId(){ 28 System.out.println(threadLocal.get().id); 29 } 30 } 31}
результат операции:
10 21 32 43 5...省略... 6440 7441 8442 9443 10444 11Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space 12 at com.gentlemanqc.MemoryLeak$TestClass.<init>(MemoryLeak.java:33) 13 at com.gentlemanqc.MemoryLeak$1.run(MemoryLeak.java:16) 14 at java.lang.Thread.run(Thread.java:745)
Небольшая модификация приведенного выше кода, см.:
1public class MemoryLeak { 2 3 public static void main(String[] args) { 4 new Thread(new Runnable() { 5 @Override 6 public void run() { 7 for (int i = 0; i < 1000; i++) { 8 TestClass t = new TestClass(i); 9 t.printId(); 10 t.threadLocal.remove(); 11 } 12 } 13 }).start(); 14 } 15 16 static class TestClass{ 17 private int id; 18 private int[] arr; 19 private ThreadLocal<TestClass> threadLocal; 20 TestClass(int id){ 21 this.id = id; 22 arr = new int[1000000]; 23 threadLocal = new ThreadLocal<>(); 24 threadLocal.set(this); 25 } 26 27 public void printId(){ 28 System.out.println(threadLocal.get().id); 29 } 30 } 31}
результат операции:
10 21 32 43 5...省略... 6996 7997 8998 9999
Утечка памяти, нормальное завершение, единственное отличие в коде сравнения: t = null заменено на t.threadLocal.remove(), Ого, волшебное удаление! ! ! Автор сначала оставляет интригу, а причины пока не анализирует. Давайте сначала рассмотрим два метода, используемых в приведенном выше примере: set() и remove().
Если карта существует, добавьте текущий ThreadLocal и значение на карту.
Если карта не существует, создайте ThreadLocalMap и сохраните ее в текущем потоке.
удалить исходный код
1public void remove() { 2 ThreadLocalMap m = getMap(Thread.currentThread()); 3 if (m != null) 4 m.remove(this); 5}
Одним словом, получить ThreadLocalMap внутри текущего потока и удалить объект ThreadLocal с карты, если он существует.
резюме
На этом четыре наиболее часто используемых метода ThreadLocal закончены.Вы заметили, что каждый метод неотделим от класса, то есть ThreadLocalMap. Поэтому, чтобы лучше понять ThreadLocal, необходимо подробно изучить эту карту.
Вездесущий ThreadLocalMap
Или старые правила, давайте сначала посмотрим на аннотации к классу, перевод такой:
ThreadLocalMap — это пользовательская хэш-карта, предназначенная для сохранения локальных переменных потоков.
Его операции ограничены классом ThreadLocal и не доступны внешнему миру.
Этот класс используется для частных переменных threadLocals и inheritableThreadLocals класса Thread.
Чтобы сохранить большое количество экземпляров threadLocal с длительным временем выживания, записи хеш-таблицы используют WeakReferences в качестве типа ключа.
Как только в хеш-таблице закончится место, запись с нулевым ключом будет очищена.
Давайте посмотрим на информацию объявления класса:
1static class ThreadLocalMap { 2 3 // hash map中的entry继承自弱引用WeakReference,指向threadLocal对象 4 // 对于key为null的entry,说明不再需要访问,会从table表中清理掉 5 // 这种entry被成为“stale entries” 6 static class Entry extends WeakReference<ThreadLocal<?>> { 7 /** The value associated with this ThreadLocal. */ 8 Object value; 9 10 Entry(ThreadLocal<?> k, Object v) { 11 super(k); 12 value = v; 13 } 14 } 15 16 /** 17 * The initial capacity -- MUST be a power of two. 18 */ 19 private static final int INITIAL_CAPACITY = 16; 20 21 /** 22 * The table, resized as necessary. 23 * table.length MUST always be a power of two. 24 */ 25 private Entry[] table; 26 27 /** 28 * The number of entries in the table. 29 */ 30 private int size = 0; 31 32 /** 33 * The next size value at which to resize. 34 */ 35 private int threshold; // Default to 0 36 37 /** 38 * Set the resize threshold to maintain at worst a 2/3 load factor. 39 */ 40 private void setThreshold(int len) { 41 threshold = len * 2 / 3; 42 } 43 44 /** 45 * Construct a new map initially containing (firstKey, firstValue). 46 * ThreadLocalMaps are constructed lazily, so we only create 47 * one when we have at least one entry to put in it. 48 */ 49 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 50 table = new Entry[INITIAL_CAPACITY]; 51 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 52 table[i] = new Entry(firstKey, firstValue); 53 size = 1; 54 setThreshold(INITIAL_CAPACITY); 55 } 56}
При создании ThreadLocalMap массив типа Entry фактически строится внутри, начальный размер — 16, пороговое значение — 2/3 длины массива, тип Entry — WeakReference, и имеется слабая ссылка на объект ThreadLocal.
Почему Entry принимает тип WeakReference?
В сборке мусора Java, чтобы увидеть, нужно ли перерабатывать объект или нет, нужно увидеть, доступен ли объект. Что достижимо, так это то, можно ли получить доступ к объекту по ссылке. (Конечно, стратегия сборки мусора намного сложнее. Для простоты понимания я кратко объясню ее здесь.)
После jdk1.2 ссылки делятся на четыре типа: сильные ссылки, слабые ссылки, мягкие ссылки и виртуальные ссылки. Строгая ссылка — это наша обычно используемая Object obj = new Object(), а obj — это сильная ссылка, указывающая на пространство памяти объекта. Когда места в памяти недостаточно, сборщик мусора Java обнаруживает, что объект имеет сильную ссылку, и скорее выдает ошибку OutofMemory, чем освобождает пространство памяти, на которое ссылаются строго. Слабая ссылка, или WeakReference, означает, что когда объект имеет только слабые ссылки, указывающие на него, сборщик мусора утилизирует его независимо от того, достаточно ли текущей памяти. И наоборот, будет ли этот объект собираться мусором, зависит от того, есть ли на него сильные ссылки. ThreadLocalMap делает это, потому что не хочет влиять на свою сборку мусора, потому что хранит сам объект ThreadLocal, но отдает инициативу вызывающей стороне. использоваться.
проблема с переполнением памяти
На этом предзнаменование завершено, теперь мы можем взглянуть на приведенный выше пример с утечкой памяти. В примере после однократного выполнения кода в цикле for соответствующее состояние памяти:
состояние памяти
t — это ссылка, возвращаемая созданием объекта TestClass, временной переменной, которая извлекается после цикла for.
thread — это ссылка, возвращаемая при создании объекта Thread.Во время выполнения метода run стек временно выполняться не будет.
После вызова t=null, хотя адрес памяти MemoryLeak больше не может быть доступен через t
Адреса памяти, соответствующие TestClass@538, становятся недоступными, и сборщик мусора Java естественным образом освобождает эту память, так что не возникает ошибки утечки памяти.
Вы обнаружили, что после обработки HTTP-запроса предварительным фильтром, фильтром маршрутизации и постфильтром будет вызван метод, да, наконец, RequestContext.getCurrentContext().unset(). Зайдите в RequestContext и посмотрите:
1public void unset() { 2 threadLocal.remove(); 3}
Видя нет, снова появилось удаление артефакта. Кстати говоря, получается ли у вас, чтобы ThreadLocal правильно использовал «позу»?
Дополнительные возможности ThreadLocalMap
Автор уже писал статьи о TreeMap и HashMap, все реализации Map имеют свои собственные методы уменьшения и разрешения хеш-конфликтов. Как здесь обрабатывается ThreadLocalMap? Пожалуйста, посмотрите вниз.
Как уменьшить коллизии хэшей
Просмотрите исходный код для добавления элементов в ThreadLocalMap:
Метод 1: Метод построения
1ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 2 table = new Entry[INITIAL_CAPACITY]; 3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 4 table[i] = new Entry(firstKey, firstValue); 5 size = 1; 6 setThreshold(INITIAL_CAPACITY); 7}
Способ 2: установить метод
1private void set(ThreadLocal<?> key, Object value) { 2 3 Entry[] tab = table; 4 int len = tab.length; 5 int i = key.threadLocalHashCode & (len-1); 6 7 for (Entry e = tab[i]; 8 e != null; 9 e = tab[i = nextIndex(i, len)]) { 10 ThreadLocal<?> k = e.get(); 11 12 if (k == key) { 13 e.value = value; 14 return; 15 } 16 17 if (k == null) { 18 replaceStaleEntry(key, value, i); 19 return; 20 } 21 } 22 23 tab[i] = new Entry(key, value); 24 int sz = ++size; 25 if (!cleanSomeSlots(i, sz) && sz >= threshold) 26 rehash(); 27}
Где i — это индекс, сохраненный ThreadLocal в ThreadLocalMap, который рассчитывается как: key.threadLocalHashCode & (len-1). Давайте сначала посмотрим, что такое threadLocalHashCode?
1private final int threadLocalHashCode = nextHashCode();
Другими словами, каждый ThreadLocal будет генерировать значение int в соответствии с nextHashCode в качестве хэш-значения, а затем получать младшие N бит хэш-значения в соответствии с этим хеш-значением & (длина массива-1) (с len как 16, 16 -1 гарантирует, что все младшие четыре бита равны 1, чтобы получить младшие четыре бита самого хеш-значения), тем самым получая позицию индекса в массиве. Итак, как это уменьшает коллизии хэшей? Секрет заключается в этом методе nextHashCode.
1private static AtomicInteger nextHashCode = new AtomicInteger(); 2 3private static final int HASH_INCREMENT = 0x61c88647; 4 5private static int nextHashCode() { 6 return nextHashCode.getAndAdd(HASH_INCREMENT); 7}
Что такое 0x61c88647? Преобразование в десятичное число равно 1640531527. Преобразование 2654435769 в тип int равно -1640531527. 2654435769 равно (квадратный корень 5-1)/2, умноженный на 2 в 32-й степени. Что такое (корень 5-1)/2? это золотое сечение, которое составляет примерно 0,618. То есть 0x61c88647 понимается как золотое сечение, умноженное на 2 в 32-й степени. какая польза? Он может волшебным образом гарантировать, что хеш-значение, сгенерированное nextHashCode, будет равномерно распределено в степени 2 и менее 2 в степени 32. Давайте посмотрим пример:
1public class ThreadLocalHashCodeTest { 2 3 private static AtomicInteger nextHashCode = 4 new AtomicInteger(); 5 6 private static final int HASH_INCREMENT = 0x61c88647; 7 8 private static int nextHashCode() { 9 return nextHashCode.getAndAdd(HASH_INCREMENT); 10 } 11 12 public static void main(String[] args){ 13 for (int i = 0; i < 16; i++) { 14 System.out.print(nextHashCode() & 15); 15 System.out.print(" "); 16 } 17 System.out.println(); 18 for (int i = 0; i < 32; i++) { 19 System.out.print(nextHashCode() & 31); 20 System.out.print(" "); 21 } 22 System.out.println(); 23 for (int i = 0; i < 64; i++) { 24 System.out.print(nextHashCode() & 63); 25 System.out.print(" "); 26 } 27 } 28}
Смотрите нет, значения индекса элемента отлично хешируются в массиве, и коллизий нет.
Как разрешить коллизии хешей
ThreadLocalMap использует метод золотого сечения, что значительно снижает вероятность коллизии хэшей, но такая ситуация все же существует, и если она возникает, то как ее решить? Пожалуйста, посмотри:
1private void set(ThreadLocal<?> key, Object value) { 2 3 Entry[] tab = table; 4 int len = tab.length; 5 int i = key.threadLocalHashCode & (len-1); 6 7 // 出现哈希冲突 8 for (Entry e = tab[i]; 9 e != null; 10 e = tab[i = nextIndex(i, len)]) { 11 ThreadLocal<?> k = e.get(); 12 13 // 如果是同一个对象,则覆盖value值 14 if (k == key) { 15 e.value = value; 16 return; 17 } 18 19 // 如果key为null,则替换它的位置 20 if (k == null) { 21 replaceStaleEntry(key, value, i); 22 return; 23 } 24 25 // 否则往后一个位置找,直到找到空的位置 26 } 27 28 tab[i] = new Entry(key, value); 29 int sz = ++size; 30 if (!cleanSomeSlots(i, sz) && sz >= threshold) 31 rehash(); 32}
Когда возникает конфликт хэшей, его метод заключается в том, чтобы увидеть, является ли это одним и тем же объектом или его можно заменить, в противном случае переместиться на один бит назад и продолжить оценку.
1private static int nextIndex(int i, int len) { 2 return ((i + 1 < len) ? i + 1 : 0); 3}
Расширение
Из кода в методе set мы знаем, что есть две предпосылки для расширения ThreadLocalMap:
!cleanSomeSlots(i, sz)
size >= threshold
Количество элементов больше, чем порог для расширения, что легко понять, так что же означает еще одна посылка? Давайте посмотрим, что делает cleanSomeSlots():
1private boolean cleanSomeSlots(int i, int n) { 2 boolean removed = false; 3 Entry[] tab = table; 4 int len = tab.length; 5 do { 6 i = nextIndex(i, len); 7 Entry e = tab[i]; 8 if (e != null && e.get() == null) { 9 n = len; 10 removed = true; 11 i = expungeStaleEntry(i); 12 } 13 } while ( (n >>>= 1) != 0); 14 return removed; 15}
Аннотация к методу очень ясна.Исходя из текущей позиции элемента, просмотрите элементы в массиве, чтобы определить, является ли это «устаревшей записью». Как упоминалось ранее при объявлении информации класса ThreadLocalMap, «устаревшая запись» относится к тем записям, ключ которых равен null. Метод cleanSomeSlots должен найти их и вызвать метод expangeStaleEntry для очистки. Возвращает true, если найдено, иначе false. ты спрашиваешь: Почему расширение зависит от возвращаемого значения? Я отвечаю: Поскольку после обнаружения вызывается метод expungeStaleEntry для очистки.
1private int expungeStaleEntry(int staleSlot) { 2 Entry[] tab = table; 3 int len = tab.length; 4 5 // expunge entry at staleSlot 6 tab[staleSlot].value = null; 7 tab[staleSlot] = null; 8 size--; 9 10 // 省略 11}
Если вы видите «нет», размер будет уменьшен на единицу, а добавление элемента приведет к увеличению размера на 1. После обнаружения cleanSomeSlots один или несколько элементов будут очищены.Минимальный вычитаемый размер равен 1, поэтому, если он возвращает true, нет необходимости судить, больше ли размер порогового значения или равен ему. Что ж, как только предварительные условия соблюдены, вызывается метод rehash, а емкость в это время не расширялась:
Ха-ха, вот настоящее расширение, которое нужно расширить:
Позиция текущего вставленного элемента, нет устаревшей записи, которую необходимо очистить в будущем.
размер больше или равен пороговому значению
После очистки устаревшей записи размер больше или равен пороговому значению 3/4.
Теперь, когда условия ясны, как расширить мощность после того, как они будут выполнены?
1private void resize() { 2 Entry[] oldTab = table; 3 int oldLen = oldTab.length; 4 int newLen = oldLen * 2; 5 // 新建一个数组,按照2倍长度扩容 6 Entry[] newTab = new Entry[newLen]; 7 int count = 0; 8 9 for (int j = 0; j < oldLen; ++j) { 10 Entry e = oldTab[j]; 11 if (e != null) { 12 ThreadLocal<?> k = e.get(); 13 if (k == null) { 14 e.value = null; // Help the GC 15 } else { 16 // key不为null,重新计算索引位置 17 int h = k.threadLocalHashCode & (newLen - 1); 18 while (newTab[h] != null) 19 h = nextIndex(h, newLen); 20 // 插入新的数组中索引位置 21 newTab[h] = e; 22 count++; 23 } 24 } 25 } 26 27 // 阈值为长度的2/3 28 setThreshold(newLen); 29 size = count; 30 table = newTab; 31}
Удвойте длину для расширения, пересчитайте индекс и заодно подчистите элемент, ключ которого равен null, то есть устаревшую запись, которая больше не хранится в расширенном массиве.
Пополнить
Интересно, заметили ли вы, что когда в ThreadLocalMap происходит коллизия хэшей, он линейно проверяет, пока не найдет пустое место. Такого рода эффективность очень низка, так почему же боги Java делают это, когда пишут код? Автор считает, что в зависимости от используемого хеш-алгоритма именно благодаря nextHashCode() вероятность коллизий гарантированно будет низкой. Кроме того, ThreadLocalMap уделяет большое внимание очистке «устаревшей записи» в процессе обработки и вовремя освобождает свободную позицию, тем самым снижая неэффективность, вызванную линейным обнаружением.
Суммировать
В этой статье сказано так много, в основном для того, чтобы все поняли, как правильно использовать ThreadLocal и принцип его использования. Следующие экстры чисто интересны, можно сравнить с предыдущей авторской"Вставка элемента HashMap«Содержание внутри, дивергентное мышление. Автор знает, что уровень ограничен, если у вас есть какие-либо замечания или предложения, пожалуйста, оставьте сообщение и укажите, я очень благодарен! ! ! Наконец, спасибо за вашу постоянную поддержку, Чжу Цзинань, Ци Чен, 12 января 2019 г.