Предисловие
Проблема безопасности потока в многопользовании тонкой и неожиданной, поскольку порядок операций в многопользовательстве непредсказуем без надлежащей синхронизации. Несколько потоков, получающих доступ к одной и той же общей переменной, особенно склонны к проблемам параллелизма, особенно когда несколько потоков должны записать общую переменную, чтобы обеспечить безопасность потока,
Как правило, пользователям необходимо выполнить соответствующую синхронизацию при доступе к общим переменным, как показано на рисунке ниже:
Можно видеть, что мера синхронизации, как правило, блокируется, что требует, чтобы пользователь имел определенное понимание блокировки, что, очевидно, увеличивает бремя для пользователя. Итак, есть ли способ создания переменной, когда каждый поток обращается к ней, он обращается к переменным собственным потоком? На самом деле, TrieLocal может сделать это. Обратите внимание, что появление нитолокала, по -видимому, не решает вышеуказанные задачи.
Threadlocal предоставляется в пакете JDK. Он обеспечивает нитовые переменные. То есть, если вы создаете переменную Threadlocal, то в каждом потоке, доступа к этой переменной, будет локальная копия переменной. Когда несколько потоков управляют этой переменной, они фактически управляют переменными в своей собственной локальной памяти, тем самым избегая проблем безопасности потоков. После создания потоковой переменной,
Каждый поток скопирует переменную в локальную память, как показано на рисунке ниже:
Хорошо, теперь давайте подумаем о вопросе: принцип реализации Threadlocal, и как Threadlocal реализуется как метод изоляции потока, внутренне?
Во -первых, нам нужно посмотреть на структуру схемы с диаграммой классов, как показано на следующем рисунке:
нравиться
Как видно на приведенной выше диаграмме классов, в классе потока есть нитокалы и наследственные читатели. Оба типа переменных являются Threadlocalmap, а Threadlocalmap - это индивидуальный HashMap. По умолчанию обе переменные в каждом потоке являются нулевыми. Они будут созданы только тогда, когда поток вызовет набор Threadlocal или получите метод впервые.
Фактически, локальные переменные каждого потока не хранятся в экземпляре Threadlocal, а хранятся в переменной Threadlocals вызовой потока. Другими словами, локальные переменные типа Threadlocal хранятся в конкретном пространстве памяти потока.
Threadlocal на самом деле оболочка. Он помещает значение значения в поток вызова потока потока через метод SET и сохраняет его. Когда вызывающий поток вызывает свой метод получения, он вытащится из переменной Threadlocals текущего потока. Если вызывающий поток не завершается, то локальная переменная будет храниться в переменной Threadlocals призванного потока.
Поэтому, когда вам не нужно использовать локальные переменные, вы можете удалить локальную переменную из переменной Threadlocals текущего потока, вызывая метод удаления потоковой переменной. Некоторые люди могут спросить, почему Threadlocals разработан как структура карты? Очевидно, что каждый поток может быть связан с несколькими потоками переменных.
Далее мы можем ввести исходный код в Threadlocal, как показано в следующем коде:
В основном он смотрит на логику реализации трех методов, установленных, получения и удаления следующим образом:
Сначала посмотрим на метод Set (t var1)
public void set (t var1) {// (1) Получить текущий поток потока var2 = Thread.currentThread (); // (2) текущий поток используется в качестве клавиши, чтобы найти соответствующую переменную потока. Если обнаружено, установите Threadlocal.Threadlocalmap var3 = this.getMap (var2); if (var3! = null) {var3.set (this, var1); } else {// (3) Первый вызов создается для создания соответствующего хэшмапа для текущего потока this.createmap (var2, var1); }}Как упомянуто выше кода (1), сначала получите вызову, а затем используйте текущий поток в качестве параметра для вызова метода GetMap (var2). Код GetMap (Thread var2) выглядит следующим образом:
Threadlocal.Threadlocalmap getMap (Thread var1) {return var1.Threadlocals; }Можно видеть, что то, что делает GetMap (var2), - это получение собственных потоков потока потока, а переменная Threadlocal связана с переменной элемента потока.
Если getMap (var2) возвращается не пустым, тогда установите значение значения на потоки Threadlocals, то есть поместите значение тока переменной переменной в потоковые потоки памяти потока текущего потока. Threadlocals - это структура HashMAP, где ключ - это эталон текущего объекта экземпляра Thrownlocal, а значение - это значение, передаваемое методом SET.
Если getMap (var2) возвращает пустые, это означает, что метод SET называется в первый раз, а затем создается переменная Threadlocals текущего потока. Давайте посмотрим, что сделано в CreateMap (var2, var1)?
void createmap (поток var1, t var2) {var1.threadlocals = new Threadlocal.threadlocalmap (this, var2); }Что вы можете увидеть, так это создать переменную Threadlocals текущего потока.
Далее, давайте посмотрим на метод get (), код выглядит следующим образом:
public t get () {// (4) Получить текущий поток потока var1 = Thread.currentThread (); // (5) Получить переменную Threadlocals variable treadlocal.Threadlocalmap var2 = this.getMap (var1); // (6) Если потоки не являются нулевыми, соответствующее значение локальной переменной возвращается, если (var2! = Null) {Threadlocal.Threadlocalmap.Entry var3 = var2.getEntry (this); if (var3! = null) {object var4 = var3.value; вернуть var4; }} // (7) Если Threadlocals пуст, инициализируется переменная элемента Threadlocals текущего потока. вернуть это.setinitialValue (); }Код (4) Сначала Получите текущий экземпляр потока. Если переменная Threadlocals текущего потока не является нулевой, она непосредственно вернет локальную переменную текущего потока. В противном случае, выполнить код (7) для инициализации, а код setinitialValue () выглядит следующим образом:
private t setInitialValue () {// (8) инициализируется в NULL объект var1 = this.InitialValue (); Thread var2 = Thread.currentThread (); ThinkLocal.ThreadlocalMap var3 = this.getMap (var2); // (9) Если переменная Threadlocals переменной текущего потока не является пустой if (var3! = Null) {var3.set (this, var1); // (10) Если переменная Threadlocals текущего потока пуста} else {this.createMap (var2, var1); } return var1; }Как упомянуто выше, если переменная Threadlocals текущего потока не является пустой, то значение локальной переменной текущего потока установлено на NULL. В противном случае CreateMap вызывается для переменной CreateMap текущего потока.
Далее мы смотрим на метод void remove (), код выглядит следующим образом:
public void Remoad () {Threadlocal.Threadlocalmap var1 = this.getMap (thread.currentThread ()); if (var1! = null) {var1.remove (this); }}Как упомянуто выше, если переменная Threadlocals текущего потока не является пустой, локальная переменная, указанная в экземпляре Threadlocal в текущем потоке, удаляется.
Далее, давайте посмотрим на конкретную демонстрацию, код заключается в следующем:
/*** Создано Конг на 2018/6/6/3. */public class threadlocaltest {// (1) функция печати static void print (string str) {//1.1 Печать значения локальной переменной в локальной памяти текущей системы потока. //1.2 Очистить переменную локального в локальной памяти текущего потока //localvariable.remove (); } // (2) Создание потококальной переменной static treadlocal <string> localvariable = new Threadlocal <> (); public static void main (string [] args) {// (3) Создание потока Thread Threadone = новый поток (new Runnable () {public void run () {//3.1 Установите значение локальной переменной localVariable в потоке OneVariable.Set («Локальная переменная нить 1»); //3.2 Вызовите функцию печати («Thread1 -Sleable.Set»); System.out.println («Результат после удаления локальной переменной потока 1" + ":" + localvariable.get ()); // (4) Создание потока Thread Threadtwo = new Thread (new Runnable () {public void run () {//4.1 Установить значение локальной переменной LocalVariable в потоке One LocalVariable.Set («Локальная переменная поток 2»); //4.2 Вызовите функцию печати («Thread 2 ------»); //4.3. 2 " +": " + localvariable.get ());}}); // (5) запустить Thread ThreadOne.start (); threadtwo.start (); }}Код (2) создает трендокальную переменную;
Коды (3) и (4) создают потоки 1 и 2 соответственно;
Код (5) начинает два потока;
Код 3.1 в потоке 1 Устанавливает значение локального Varairable через метод SET. Эта настройка на самом деле является копией в локальной памяти потока 1. Эта копия не может быть получена по потоку 2. Затем код 3.2 вызовет функцию печати, а код 1.1 получает значение локального.
Поток 2 выполняется аналогично потоке 1.
Результаты работы следующие:
Здесь нам нужно обратить внимание на проблему утечки памяти Threadlocal
Каждый поток имеет элементную переменную с именем Threadlocals внутри. Тип переменной - хэшмап. Ключ - эта ссылка на изменчивую, которую мы определили, и значение - это значение, когда мы устанавливаем. Локальная переменная каждого потока хранится в собственных потоках памяти. Если текущий поток не исчезает, то эти локальные переменные будут храниться до.
Следовательно, это может вызвать утечку памяти, поэтому после ее использования не забудьте вызвать метод удаления нитокала, чтобы удалить локальные переменные в потоке потока соответствующего потока.
После распаковки комментариев в коде 1.2 запустите снова, и результат запуска выглядит следующим образом:
Мы когда -нибудь задумывались о таком вопросе: получаем ли мы значение потоковой переменной, установленной в родительском потоке в детском потоке?
Здесь мы можем сказать вам, что значение потоковой переменной, установленной в родительском потоке, не может быть получено в детском потоке. Итак, есть ли способ получить дочернюю поток для доступа к значению в родительском потоке? Унаследованная чистота появилась в будущем. Унаследованные наследники от Threadlocal и предоставляют функцию, к которой дети могут получить доступ к локальным переменным, установленным в родительском потоке.
Во -первых, давайте перейдем к исходному коду класса «Наследия», чтобы прочитать следующее:
открытый класс enhytableThreadlocal <T> extends threadlocal <t> {public inehytableThreadLocal () {} // (1) Защищенный t childvalue (t var1) {return var1; } // (2) Threadlocalmap getMap (Thread var1) {return var1.InheritableTreadLocals; } // (3) void createmap (Thread var1, t var2) {var1.InheritableTreadlocals = new ThreadlocalMap (this, var2); }}Вы можете увидеть, что наследственные чистого отчисления наследуют нитолокальные и переписанные три метода. Код выше был отмечен. Код (3) Можно видеть, что inheritableThreadLocal переписывает метод CreateMap, поэтому можно увидеть, что когда метод установки вызывается в первый раз, экземпляр переменной inheritableThreadLocals текущего потока, а не потока.
Код (2) Вы можете знать, что при получении метода GET для получения внутренней переменной карты текущего потока получается honehoritableTreadlocals, а не потоки.
Ключевой момент здесь, когда выполняется переписанный код (1), и как реализовать, что детский поток может получить доступ к локальным переменным родительским потоком. Начиная с кода, созданного потоком, конструктор по умолчанию потока и конструктор Thread.java Class заключаются в следующем:
/*** Создано Конг на 2018/6/6/3. */ public Thread (Runnable Target) {init (null, target, "Thread-" + nextThreadnum (), 0); } private void init (ThreadGroup G, выполнение цели, имени строки, long StackSize, AccessControlContext acc) {// ... // (4) Получить текущий поток потока Parent = currentThread (); //... //(5) If the inheritableThreadLocals variable of the parent thread is not null if (parent.inheritableThreadLocals != null) //(6) Set inheritableThreadLocals variable in the child thread this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stacksize = stacksize; tid = nextThreadId (); }При создании потока метод init будет вызван в конструкторе. Как упоминалось ранее, GET и SET -метод honehoritableThreadLocal Class управляет переменной inehytableThreadLocals, поэтому переменная inheritableThreadLocal здесь не является нулевой, поэтому будет выполнен код (6). Давайте посмотрим на исходный код метода CreateInheritedMap следующим образом:
static Threadlocalmap createInheritedMap (Threadlocalmap parentMap) {return new Threadlocalmap (parentMap); }Вы можете видеть, что CreateInheritedMap использует переменную inheritableTreadLocals родительского потока в качестве конструктора для создания новой переменной ThreadLocalMAP, а затем присваивает ее переменной honehoritableThreadLocals детского потока. Затем введите конструктор Threadlocalmap. Исходный код заключается в следующем:
private Threadlocalmap (Threadlocalmap ParentMap) {intry [] parenttable = parentmap.table; int len = parenttable.length; SetThreshold (Len); Таблица = Новая запись [Лен]; for (int j = 0; j <len; j ++) {inpit e = parenttable [j]; if (e! = null) {@suppresswarnings ("unchecked") Threadlocal <Object> key = (Threadlocal <Object>) e.get (); if (key! = null) {// (7) вызовите значение объекта переопределенного метода = key.childvalue (e.value); // return e.value intport c = new Entry (key, value); int h = key.threadlocalhashcode & (len - 1); while (таблица [h]! = null) h = nextIndex (h, len); Таблица [h] = c; размер ++; }}}}}}Приведенный выше код скопирует значение переменной члена родительского потока neheritableThreadLocals для нового объекта ThreadLocalMAP, а код (1), переписанный кодом (7), также входит в представление кода.
В общем: inehytableThreadLocal Class переписывает код (2) и (3) и сохраняет локальные переменные в переменную inehytableThreadLocals конкретного потока. Когда поток устанавливает переменную через метод SET или получите метод inehytableThreadLocals, он создаст переменную inehritableThreadLocals текущего потока. Когда родительский поток создает детскую ветку,
Конструктор скопирует локальную переменную в переменной honehoritableThreadLocals в родительском потоке и скопирует ее в переменную inehytableThreadLocals в потоке дочернего потока.
После того, как принцип будет хорошо понят, давайте возьмем пример, чтобы проверить то, что мы знаем выше, следующим образом:
Пакет com.hjc;/*** Создан Конг на 2018/6/6/3. */открытый класс enhytableThreadLocaltest {// (1) Создать переменную потока public static threadlocal <string> threadlocal = new Threadlocal <string> (); public static void main (string [] args) {// (2) Установить переменную потока threadlocal.set ("hello java"); // (3) Поток потока запуска дочернего потока = новый поток (new Runnable () {public void run () {// (4) Значение детского потока выводит систему переменной потока. Thread.Start (); // (5) Основной поток выводит систему значения переменной потока. }}Результаты работы следующие:
То есть после того, как в родительском потоке установлена та же нивая переменная, ее нельзя получить в детском потоке. Согласно введению в предыдущем разделе, это должно быть нормальным явлением, поскольку текущий поток является дочерним потоком, когда дочерний поток вызывает метод GET, а метод SET вызывается для установки переменной потока является основным потоком. Эти два - это разные потоки, и, естественно, дочерняя нить возвращает ноль при доступе.
Итак, есть ли способ получить дочернюю поток для доступа к значению в родительском потоке? Ответ - да, так что используйте наследственную чистоту, проанализированную по нашим вышеупомянутым принципам.
Измените код (1) приведенного выше примера на:
// (1) Создать переменную потока public static threadlocal <string> threadlocal = new inehytableThreadlocal <string> ();
Результаты работы следующие:
Можно видеть, что значение переменной потока теперь можно получать обычно из детского потока. Итак, при каких обстоятельствах детям нужно получить нитолокальные переменные из родительского потока?
Существует довольно много ситуаций, таких как потоковая переменная, в которой хранится информация пользователя. Весьма вероятно, что подгруппы также должны использовать информацию пользователя. Например, некоторое промежуточное программное обеспечение должно использовать унифицированный идентификатор отслеживания для записи всей вызовой ссылки.
Использование Threadlocal в весеннем запросе
Мы знаем, что при настройке бобов в XML весной вы можете указать атрибут Scope для настройки объема бобов, чтобы быть синглтоном, прототипом, запросом, сеансом и т. Д. Если вы хотите, чтобы боб в вашем весеннем контейнере имел некоторый объем сети,
В дополнение к соответствующим атрибутам области, необходимых для настройки уровня бобов, он также должен быть настроен в web.xml следующим образом:
<Learser> <lloader-class> org.springframework.web.context.request.RequestContextListener </sluster-class> </sluster>
Здесь мы в основном рассмотрим два метода запроса contextlistener:
public void requestInitialized (servletrequestevent requestevent)
и
public void requestDestroyed (servletrequestevent requestevent)
Когда появится веб -запрос, будет выполнено метод запроса, инициализированного:
public void requestInitialized (servletrequestevent requestevent) {..... omit httpservletrequest request = (httpservlectrequest) requestevent.getservletrequest (); ServletRequestattributes attributes = new ServletRequestattributes (запрос); request.setattribute (request_attributes_attribute, attributes); Localecontextholder.setlocale (request.getlocale ()); // Установить атрибут на Threadlocal переменную request ontextholder.setrequestattributes (attributes); } public static void setRequestattributes (requestAttributes attributes) {setRequestattributes (атрибуты, false); } public static void setRequestattributes (requestAttributes attributes, boolean penaitible) {if (attributes == null) {resetRequestattributes (); } else {// default inerailitable = false if (наследственное) {enhytableRequestattributeSholder.set (attributes); requestattributeSholder.remove (); } else {requestAttributeSholder.set (attributes); enhytableRequestattributeSholder.remove (); }}}Вы можете увидеть приведенный выше исходный код. Поскольку наследуемое по умолчанию является ложным, наши значения атрибутов размещены в запросе requestTtributeShoder, а его определение:
Private Static Final Threadlocal <seekAttributes> requestAttributeSholder = new newThreadlocal <SearchAttributes> ("атрибуты запроса"); Частный статический окончательный Threadlocal <seekAttributes> inehytableRequestattributeSholder = new именование именоритаСреди них, названный threadlocal <t> расширяет Threadlocal <t>, так что это не наследственное.
Среди них, названный threadlocal <t> расширяет Threadlocal <t>, так что это не наследственное.
NameinheritableThreadLocal <T> Extends inhoritableThreadLocal <T>, поэтому он имеет наследование, поэтому значение атрибута, размещенное в запросе concontextholder по умолчанию, не может быть получено в детском потоке.
Когда запрос заканчивается, вызывается метод запроса, и исходный код выглядит следующим образом:
public void requestDestroyed (servletRequestevent requestEvent) {servletRequestattributes attributes = (servletRequestattributes) requestEvent.getServletRequest (). getAttribute (request_attributes_attribute); ServletRequestattributes ThreadAttributes = (ServletRequestattributes) requestOntextholder.getRequestattributes (); if (ThreadAttributes! = null) {// Мы, скорее всего, очистите поток текущего потока в потоке начального запроса if (attributes == null) {attributes = thinkattributes; } // Когда запрос заканчивается, очистите переменную потока текущего потока. Localecontextholder.resetlocalecontext (); RequestContextholder.ResetRequestattributes (); } if (attributes! = null) {attributes.requestcompleted (); }}Далее, давайте посмотрим на логику призывов веб -запроса из таблицы времени:
То есть каждый раз, когда инициируется веб -запрос перед обработкой контекста (конкретного приложения) в Tomcat, свойство RequestContexTholder будет установлено после соответствия хоста, так что запрос QuestribitibuteDer не пуст и будет очищен в конце запроса.
Следовательно, по умолчанию дочерние потоки атрибута, размещенные в запросе, не могут быть доступны, а бобы в объеме запроса пружины реализованы с использованием Threadlocal.
Далее выполняется пример запроса на моделирование, код заключается в следующем:
Конфигурация web.xml выглядит следующим образом:
Поскольку это область запроса, это должен быть веб -проект, и requestContextListener должен быть настроен на web.xml.
<Learser> <lloader-class> org.springframework.web.context.request.RequestContextListener </sluster-class> </sluster>
Затем введите бобы для запроса в контейнер IOC. Код заключается в следующем:
<bean id = "requestbean" scope = "request"> <name = "name" value = "hjc" /> <aop: scoped-proxy /> < /bean>
Тестовый код заключается в следующем:
@Webresource ("/testservice") public class testrpc {@autowired private requestbean requestInfo; @Resourcemapping ("test") public actionResult test (context errorcontext) {actionResult result = new ActionResult (); pvginfo.setname ("hjc"); String name = requestInfo.getName (); result.setValue (имя); результат возврата; }}Как упомянуто выше, сначала настройте requestContextListener в web.xml, затем введите экземпляр requestbean в контейнер IOC с помощью объема запроса. Наконец, экземпляр requestbean вводится в TestRPC. Метод Тест Сначала вызывает метод requestInfo setName, чтобы установить атрибут имени, затем получить атрибут имени и вернуть.
Здесь, если объект RequestInfo является Singleton, после того, как несколько потоков вызовут метод испытания одновременно, каждый поток является операцией SET-GET. Эта операция не является атомной и вызовет проблемы безопасности потока. Область, объявленная здесь, является уровнем запроса, и в каждом потоке есть локальная переменная с requestInfo.
Таблица времени запроса метода выше приведенного выше следующего:
Нам нужно сосредоточиться на том, что происходит при вызове теста:
Фактически, запрос, созданный ранее, - это после того, как вы заинтересованы CGLIB (если вы заинтересованы, вы можете изучать ScopedProxyFactorybean и другие типы), поэтому, когда вы вызовуте SetName или GetName, вы будете перехвачены DynamicAdvisedInterceptor. Перехватчик в конечном итоге вызовет метод Get refectScope, чтобы получить локальные переменные, удерживаемые текущим потоком.
Ключ здесь. Нам нужно посмотреть на исходный код метода запроса, полученных следующим образом:
public Object get (string name, objectfactory objectFactory) {requestAttributes attributes = requestContextholder.currentRequestattributes (); // (1) Object scopedObject = attributes.getAttribute (name, getScope ()); if (scopedObject == null) {scopedObject = objectFactory.getObject (); // (2) attributes.setAttribute (name, scopedObject, getScope ()); // (3)} return scopedObject; }Можно видеть, что когда запрос будет инициирован, requestAttributeSholder будет установлен путем вызова requestContextListener.Requestinitialized в requestContextListener.setRequestTtributess.
Затем после того, как запрос направляется на метод тестирования TestRPC, в первый раз, когда метод SETNAME вызывается в методе испытания, в конечном итоге будет вызван метод requestScope.get (). Код в методе GET (1) получает значение установленного атрибута, сохраненного с помощью потоковой переменной requestAttributeSterer через requestContextListener.RequestInitialize.
Затем проверьте, есть ли атрибут с именем requestInfo в наборе атрибутов. Поскольку это первый вызов, его не существует, поэтому код будет выполнен (2) и пусть Spring создает объект RequestInfo, а затем установит его на атрибуты набора атрибутов, то есть он будет сохранен в локальной памяти текущего потока запроса. Затем верните созданный объект и вызовите SetName созданного объекта.
Наконец, метод getName вызывается в методе испытания, и будет вызван метод requestScope.get (). Код в методе GET (1) получает поток локальной переменной requestTributes через requestContextListener.Requestinitialize, а затем посмотрите, есть ли атрибут с именем requestInfo в наборе атрибутов.
Поскольку Bean с именем requestInfo был установлен на переменную Threadlocal, когда она вызывает для SetName в первый раз, и тот же поток вызывается для SetName и GetName, объект RequestInfo, созданный при вызове SETNAME, прямо здесь возвращается, и затем его метод getName вызывается.
До сих пор мы поняли принцип реализации Threadlocal и указали, что Threadlocal не поддерживает наследование; Затем мы немедленно объяснили, как наследственная чистота компенсирует функцию, что Threadlocal не поддерживает наследование; Наконец, мы кратко представили, как использовать Threadlocal в Spring Framework для реализации бобов Reqeust Scope.
Суммировать
Вышеуказанное - все содержание этой статьи. Я надеюсь, что содержание этой статьи имеет определенную справочную ценность для каждого обучения или работы. Если у вас есть какие -либо вопросы, вы можете оставить сообщение для общения. Спасибо за поддержку Wulin.com.