Здравствуйте, меня зовут да.
Вслед за предыдущимЯ написал все, что может спросить ThreadLocalПосле этого давайте поиграем в другой набор FastThreadLocal, который представляет собой расширенную версию ThreadLocal, собственного колеса Netty для ThreadLocal, поэтому, если вы не полностью понимаете ThreadLocal, рекомендуется сначала прочитать предыдущую статью, чтобы заложить основу.
Узнав о FastThreadLocal, вы, возможно, сможете предоставить некоторые идеи для некоторых оптимизаций в будние дни, или вы можете установить икс на собеседовании.
Интервьюер: ThreadLocal имеет тот же недостаток, что и xxx, так как же его оптимизировать?
Вы просто BB реализуете FastThreadLocal, разве это не безопасно?
Итак, сегодня мы рассмотрим, как Netty реализует FastThreadLocal, Без лишних слов, план этой статьи выглядит следующим образом:
- Подсчитайте недостатки ThreadLocal.
- Как его улучшить для устранения недостатков ThreadLocal?
- Принцип FastThreadLocal.
- Практическая работа FastThreadLocal VS ThreadLocal.
После этой статьи расширенная версия ThreadLocal в основном победила.В следующей статье я сделаю расширение на основе этой статьи, относительно низкоуровневое расширение, которое относится к типу, который абсолютно устанавливает x.Вы узнаете, когда вы читай статью,а я яму зарою.Да хаха.
Отъезд, отъезд!
Подсчет недостатков ThreadLocal
После прочтения предыдущей статьи учащиеся должны четко понимать один из недостатков ThreadLocal: линейный метод обнаружения используется для конфликтов хэшей, что неэффективно.
Как видно из рисунка видно, что вакансия была найдена после двух обходов, чем больше конфликтов, тем больше обходов нужно пройти. И в следующий раз, когда вы его получите, местоположение, в которое попадет хеш, не будет искомой записью, поэтому он продолжит обход и поиск в обратном направлении, так что это неэффективно.
Например, HashMap использует метод связанного списка для разрешения конфликтов, и чтобы предотвратить рост накладных расходов на обход связанного списка, он будет преобразован в красно-черное дерево для поиска по определенным условиям.Такое решение должно быть при условии частых конфликтов, лучше линейного метода обнаружения, так что это направление оптимизации.
Но FastThreadLocal так не оптимизирован, мы поговорим об этом позже.
Другим недостатком является то, что ThreadLocal использует WeakReference, чтобы гарантировать, что ресурсы могут быть освобождены, но это может привести к тому, что некоторые ключи Etnry будут нулевыми, то есть существует бесполезная запись.
Поэтому при вызове метода get или set класса ThreadLocal он будет активно очищать бесполезную запись, чтобы уменьшить вероятность утечек памяти.
На самом деле это эквивалентно добавлению накладных расходов на очистку при получении и установке.Если во время получения очищается слишком много бесполезных записей, то на этот раз получение будет относительно медленным.
Также есть проблема с утечками памяти, конечно, эта проблема существует только при использовании пулов потоков, и выше также упоминалось, что при использовании get и set некоторые бесполезные ключи могут быть очищены, так что это не так уж и преувеличено, просто не забудьте использовать После вызова ThreadLocal#remove не будет проблем с утечкой памяти.
Примерно так.
Как исправить недостатки ThreadLocal
Итак, как его изменить?
Как упоминалось выше, линейный метод обнаружения конфликта хэшей ThreadLocal не годится, а слабая ссылка Entry может вызвать утечку памяти.Все это связано с ThreadLocalMap, поэтому необходима новая карта для замены ThreadLocalMap.
И этот ThreadLocalMap является переменной-членом в Thread, поэтому Thread нужно переместить, но мы не можем изменить код Thread, поэтому нам нужно получить новый Thread для сопоставления.
Таким образом, нам нужно не только получить новый ThreadLocal, ThreadLocalMap, но также получить соответствующий поток, чтобы использовать новый ThreadLocalMap.
Поэтому, если вы хотите улучшить ThreadLocal, вам нужно переместить эти три класса.
Реализация, соответствующая Netty, — это FastThreadLocal, InternalThreadLocalMap, FastThreadLocalThread.
Тогда подумайте об этом. Поскольку эффект линейного обнаружения конфликта хэшей не очень хорош, вы можете легко подумать об упомянутом выше методе связанного списка, а затем изменить его на красно-черное дерево на основе метода связанного списка. аспект, но подумайте еще раз.
Например, пусть Hash не конфликтует, поэтому разработайте алгоритм хеширования, который не конфликтует? несуществующий!
Так как же не быть конфликтующим?
Займите свое место
В чем смысл? То есть каждый раз, когда новый объект FastThreadLocal вставляется в InternalThreadLocalMap, объекту выдается уникальный индекс, а затем объект запоминает индекс.Когда вы переходите к InternalThreadLocalMap, чтобы найти значение, вы можете напрямую использовать индекс для получить соответствующее значение.
Не будет ли конфликта?
Это решение предоставлено FastThreadLocal, которое проанализировано ниже.
Так же есть проблема с утечкой памяти.На самом деле пока используется стандартное использование,удаляйте его после использования.На самом деле это не очень хорошее решение.Однако кривая FastThreadLocal спасла страну,и это тоже зависит от анализ ниже!
Принцип FastThreadLocal
Следующий анализ Netty основан на версии 4.1.
Давайте сначала посмотрим на определение FastThreadLocal:
Вы можете видеть, что есть переменнаяToRemoveIndexчлен класса, и оформлен с помощью final, поэтому он равен каждому FastThreadLocal, имеет общее неизменяемое значение int, и это значение будет проанализировано позже.
Затем я вижу, что этого индекса нет, он назначается при построении FastThreadLocal, и он также является окончательным Modified, поэтому он не является переменным,Этот индекс — это то, что я сказал выше, чтобы отправить уникальный индекс каждому новому FastThreadLocal., чтобы каждый индекс знал свою позицию.
Вышеуказанные два индекса передаютсяInternalThreadLocalMap.nextVariableIndex()
Присвоение, слепое предположение, это должно быть достигнуто за счет атомарных приращений класса.
Давайте посмотрим на реализацию:
Действительно, статический атомарный класс также определен в InternalThreadLocalMap, который возвращается и увеличивается каждый раз, когда вызывается nextVariableIndex.Другой операции присваивания нет.Отсюда мы также можем знать, что значение variableToRemoveIndex равно 0, потому что оно принадлежит константному присваиванию , Первый вызов Значение nextIndex равно 0.
Увидев это, я не знаю, все ли чувствовали себя немного не так. Кажется, это пустая трата места, давайте продолжим смотреть вниз.
InternalThreadLocalMap является целью предыдущего ThreadLocalMap, который представляет собой класс недостатков ThreadLocal, на который необходимо обратить внимание.
Давайте еще раз рассмотрим определение ThreadLocalMap.
Это массив Entry, и Entry слабо ссылается на ThreadLocal как на ключ.
А InternalThreadLocalMap немного отличается:
Как видите, InternalThreadLocalMap, кажется, отказался от формы карты, не определяя ключ и значение, но представляет собой массив объектов?
Так как же он сохраняет FastThreadLocal и соответствующее значение через Object? Начнем анализ с FastThreadLocal#set:
Поскольку мы уже знакомы с подпрограммой ThreadLocal, мы знаем, что InternalThreadLocalMap должна быть переменной в FastThreadLocalThread.
Затем после того, как мы получим карту из соответствующего FastThreadLocalThread, нам нужно выполнить операцию заполнения, а именноsetKnownNotUnset
.
Давайте сначала взглянем на вещи внутри операции.setIndexedVariable
метод:
Видно, что уникальный индекс, сгенерированный входящей конструкцией FastThreadLocal, может напрямую найти индекс из массива Object и заменить его, так что конфликта вообще не будет, а логика очень проста и совершенна.
Затем, если вставленное значение не НЕУСТАНОВЛЕНО (значение по умолчанию), выполнитеaddToVariablesToRemove
метод, в чем польза этого метода?
Это выглядит немного странно? Что это за операция? Не волнуйтесь, посмотрите, как я нарисую картинку, чтобы объяснить:
Это основная диаграмма отношений массива Object.Набор помещается в первую позицию, и все используемые объекты FastThreadLocal сохраняются в наборе, а затем значение помещается в конец массива.
Так зачем создавать набор для хранения всех используемых объектов FastThreadLocal?
для удаления, вы подумайте, если вы хотите очистить все FastThreadLocal в потоке сейчас, должно быть место для хранения этих объектов FastThreadLocal, чтобы вы могли найти этих парней и убить их.
Так что просто освободите первую позицию массива и поместите набор для сохранения этих объектов FastThreadLocal.Если вы хотите удалить все объекты FastThreadLocal, вам нужно только пройти набор, получить индекс FastThreadLocal, найти соответствующую позицию массива и очистить значение, а затем удалить FastThreadLocal из набора.
Бывает, что этот метод реализован в FastThreadLocal, давайте посмотрим:
Содержание может быть слишком большим, давайте подведем итоги и рационализируем то, что я сказал выше:
Во-первых, InternalThreadLocalMap не использует метод хранения ThreadLocalMap kv, а использует массив Object для хранения объектов FastThreadLocal и их значений, в частности, набор, содержащий используемые объекты FastThreadLocal, сохраняется в первой позиции, а затем все значения сохраняются позже. .
Причина, по которой необходим набор, заключается в том, чтобы хранить все используемые объекты FastThreadLocal, чтобы эти объекты можно было найти для последующего удаления.
Причина, по которой значение может быть сохранено непосредственно в других местах массива, заключается в том, что каждому FastThreadLocal при создании назначается уникальный индекс, и этот индекс соответствует индексу, в котором находится значение.
Увидев это, мне интересно, чувствуете ли вы пустую трату пространства?
Я привожу пример.
Предполагая, что в системе есть 100 новых FastThreadLocal, индекс 100-го FastThreadLocal равен 100, что не должно вызывать сомнений.
Из приведенного выше метода set мы можем знать, что InternalThreadLocalMap будет извлечен из текущего потока только при вызове set, а затем значение будет вставлено в массив этой карты.Здесь мы рассмотрим метод set.
Так что же здесь имеется в виду?
Если я не подключил этот поток до FastThreadLocal , я подключу его сейчасПервыйFastThreadLocal, длина построенного массива равна 32, но индекс этого FastThreadLocal вырос до 100, поэтомуПервый раз этот поток подтыкает значение, а там только такое значение, массив нужно расширить.
Понимаете, это то, что я называю расточительством, растратой пространства.
Разработчики, связанные с Netty, знают, что это пустая трата места.Таким образом, расширение массива основано на индексе, а не на размере исходного массива., видите ли, если он основан на расширении исходного массива, то первое расширение удваивается, 32 становится 64, а данные с индексом 100 все равно не могут быть заполнены, поэтому приходится расширять один раз, что не красиво .
Итак, вы можете видеть, что параметр, переданный для раскрытия, — это index.
Как видите, оно напрямую округляется до степени 2 на основе индекса. Затем идет копия расширения, вот прямая копия массива,Нет необходимости перефразировать, а расширение ThreadLocalMap требует повторного хеширования, то есть перераспределения местоположения на основе хеш-значения ключа, поэтомуЭто также означает, что FastThreadLocal лучше, чем ThreadLocal..
Кстати, приведенная выше операция округления до степени 2, не знаю, знакомы ли вы с ней, это согласуется с реализацией HashMap.
Кхм, но у меня нет доказательств, я могу только сказать, что отличный коддолгая история.
Таким образом, из приведенной выше реализации мы можем знать, что Netty специально разработана таким образом, используя дополнительное пространство для обмена набором и получением, которые не будут конфликтовать, поэтому скорость записи и получения выше, что типично.пространство для времени.
Что ж, к этому моменту вы, должно быть, поняли основной принцип FastThreadLocal, давайте взглянем на реализацию метода get, я думаю, вы сможете компенсировать эту реализацию.
Да ничего сложного, индекс это тот индекс, который был заранее задан при построении FastThreadLocal, а дальше идет поиск по индексу массива напрямую, если он не найден, то вызывается метод init для его инициализации.
Давайте продолжим исследовать здесьInternalThreadLocalMap.get()
, который сделал совместимым. Но сначала я хочу представить FastThreadLocalThread, который заменяет Thread.
Вы можете видеть, что он наследует Thread и имеет переменную-член, которая является InternalThreadLocalMap, о которой мы упоминали ранее.
Тогда давайте посмотрим на метод get Я вырезал несколько, но логика очень проста.
Причина, по которой fastGet и slowGet разделены здесь для совместимости, Допустим, есть незнакомый человек, который использует FastThreadLocal, но не использует FastThreadLocalThread, а затем, когда он вызывает FastThreadLocal#get, он переходит в Thread, чтобы найти InternalThreadLocalMap, не глупо ли это ?, сообщит об ошибке.
Поэтому я сделал еще один slowThreadLocalMap, который является ThreadLocal, который сохраняет InternalThreadLocalMap для совместимости с этой ситуацией.
Отсюда мы также можем узнать, что FastThreadLocal лучше всего использовать в связке с FastThreadLocalThread, иначе он будет разделен слоем.
FastThreadLocal<String> threadLocal = new FastThreadLocal<String>();
Thread t = new FastThreadLocalThread(new Runnable() { //记得要 new FastThreadLocalThread
public void run() {
threadLocal.get();
....
}
});
Что ж, две основные операции get и set были проанализированы, давайте, наконец, взглянем на операцию удаления.
Очень просто, правильно, перезаписать значение в массиве, а потом удалить соответствующий объект FastThreadLocal в наборе.
Однако, увидев это, некоторые люди могут задаться вопросом, а как же быть с утечками памяти?
На самом деле вы можете видеть, что FastThreadLocal не использует слабые ссылки, поэтому он доверяет очистку бесполезного FastThreadLocal нормативному использованию, то есть активно вызывает метод удаления, когда он бесполезен.
Но это немного спасает страну, давайте взглянем на класс FastThreadLocalRunnable:
Я нарисовал ключевые моменты. Вы можете видеть, что после выполнения этого Runnable он будет активно вызывать FastThreadLocal.removeAll() для очистки всех FastThreadLocals. Это то, что я сказал о спасении страны по кривой. Да ладно, это так сладкий.
Конечно, предполагается, что вы можете использовать не Runnable, а FastThreadLocalRunnable. Но здесь нетти также инкапсулирована.
Netty реализует фабричный класс DefaultThreadFactory для создания потоков.
Видите ли, вы переходите в Runnable, верно? Все в порядке, я упаковываю его как FastThreadLocalRunnable, а поток, который я возвращаю, относится к типу FastThreadLocalThread, что позволяет в значительной степени избежать ошибок и снизить сложность использования.
Это одно из преимуществ шаблона проектирования фабричный метод. Поэтому, если мы боимся, что другая сторона окажется бесполезной в инженерном отношении, мы инкапсулируем ее, а затем позволяем использовать ее другим, что также экранирует некоторые детали.Он хороший и ты тоже.
Итак, посмотрите на исходный код фреймворка с открытым исходным кодом, там есть чему поучиться! Ну, принцип FastThreadLocal примерно здесь.
FastThreadLocal VS ThreadLocal
На данный момент мы полностью поняли разницу между ними, но насколько быстр Fast?
Мы используем эксперименты, чтобы говорить.В исходном коде Netty уже есть тест, поэтому мы можем просто запустить его напрямую.
В нем два эксперимента:
FastPath соответствует использованию объекта потока FastThreadLocalThread.
SlowPath соответствует использованию объекта потока Thread.
Оба эксперимента определяют ThreadLocal и FastThreadLocal соответственно:
Посмотрим на результат выполнения:
FastPath:
SlowPath:
Видно, что пропускная способность при использовании FastThreadLocal с FastThreadLocalThread действительно больше, чем при использовании ThreadLocal, но не кажется, что она намного больше?
Но я видел сравнение бенчмарков других людей в интернете, тот же код, его результат в три раза больше.
В любом случае, я запускал его несколько раз, и каждый раз он был лучше, чем собственная пропускная способность ThreadLocal, но не намного лучше... как-то странно.
Что касается FastThreadLocal с Thread, пропускная способность меньше, чем ThreadLocal, что указывает на то, что использование FastThreadLocal должно быть согласовано с FastThreadLocalThread, в противном случаеобратная оптимизация.
Код находится в проекте netty microbench.Если вам интересно, вы можете спуститься и запустить его самостоятельно.
Наконец
Подведем итог еще раз:
- FastThreadLocal напрямую находит значение, присваивая индексы, не возникает конфликта хэшей, а эффективность высока.
- FastThreadLocal использует пространство вместо времени для повышения эффективности.
- FastThreadLocal необходимо использовать с FastThreadLocalThread, иначе он не так хорош, как собственный ThreadLocal.
- FastThreadLocal лучше всего использовать с FastThreadLocalRunnable, так что после выполнения задачи будет активно вызываться removeAll для удаления всех FastThreadLocal для предотвращения утечек памяти.
- Также рекомендуется использовать FastThreadLocal для вызова удаления после использования.
Это расширенная версия ThreadLocal, реализованная Netty.Если вы прочитаете исходный код Netty, вы обнаружите, что существует довольно много сценариев, в которых ThreadLocal используется внутри, поэтому эта оптимизация по-прежнему необходима.
И количество потоков по умолчанию в пуле рабочих потоков Netty вдвое превышает количество ядер ЦП, поэтому потоков не будет слишком много, поэтому пустая трата места не будет большой, поэтому эта волна времени обмена пространством малоэффективна. .
Хорошо, это все для статьи. Копая яму, я обнаружил в классе InternalThreadLocalMap несколько странных длинных переменных.
Знающие учащиеся могут знать, что это делается для того, чтобы заполнить строку кэша и избежать ложных проблем с совместным использованием.
хорошо, а почему он помечен как @deprecated? И сказал, что будущая версия будет удалена?
И слушайте следующее разложение.
Рекомендуемое чтение:Я написал все, что может спросить ThreadLocal
Я да, от чуть-чуть до миллиарда чуть-чуть, увидимся в следующей статье!