In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/01 Report--
How to carry out ThreadLocal source code analysis, I believe that many inexperienced people are at a loss about this. Therefore, this paper summarizes the causes and solutions of the problem. Through this article, I hope you can solve this problem.
Introduction to 1.ThreadLocal
There can be multiple ThreadLocal within a thread, but it maintains the same ThreadLocalMap variable of the thread and shares the same Entry array.
ThreadLocal data structure:
There is a ThreadLocalMap attribute within each thread that ThreadLocal maintains to ensure data sharing within a single thread. Inside ThreadLocalMap, there is an entry array, which is a key, value structure, key is a weak reference to the current ThreadLocal, value is used to store specific values, the type is a generic structure, and supports a variety of data variables. The subscript value of the Entry array in ThredLocalMap is also determined by key.threadLocalHashCode & (array length-1), but this threadLocalHashCode is determined by AutomicLong incrementing 0x61c88647 each time, which minimizes hash collisions. Unlike HashMap,ThreadLocalMap, only one Entry array is maintained internally, so when a hash conflict occurs, ThreadLocalMap will place the Entry with the hash conflict in the first empty array slot after the current key corresponding array subscript. The expansion threshold of ThreadLocal defaults to 2 of the array size of 3. However, when we call the set,get,remove method of threadlocal, the operation of recycling expired key occurs in ThreadLocalMap, but this kind of collection is a kind of sampling collection, which may not be able to recover all expired key. The default initialization length of the Entry array is 16.
2.ThreadLocal simple example public class ThreadLocalTest {private static final ThreadLocal threadLocal = new ThreadLocal (); private static String str = null; public static void print1 () {System.out.println ("print method 1 output:" + threadLocal.get ());} public static void print2 () {System.out.println ("print method 2 output:" + str) } public static void main (String [] args) {/ / Thread 1 new Thread (()-> {threadLocal.set ("str1 set by Thread 1"); str = "str2 set by Thread 1"; / / sleep for 5 seconds try {Thread.sleep (5000) } catch (InterruptedException e) {e.printStackTrace ();} / / print after sleeping for 5 seconds, when the second thread has already finished executing print1 (); print2 ();}. Start () / / Thread 2 new Thread (()-> {threadLocal.set ("str1 set by Thread 2"); str = "str2 set by Thread 2"; / / print print1 (); print2 ();}) .start ();}}
Running result:
Print method 1 output: str1 print method 2 set by thread 2 output: str2 print method 1 set by thread 2 output: str1 print method 2 set by thread 1 output: str2 set by thread 2
3.ThreadLocal Source Code Analysis attribute Analysis of 3.1.ThreadLocal
Fibonacci hashing is used in ThreadLocal to ensure the discreteness of the hash table. It can guarantee that the hash value generated by nextHashCode is uniformly distributed on the power of 2. The specific mathematical problems are not delved into here.
Private final int threadLocalHashCode = nextHashCode (); private static AtomicInteger nextHashCode = new AtomicInteger (); / / Decimal 1640531527private 0.618 * 2 ^ 32, this value is the golden ratio * 2 ^ 32private static final int HASH_INCREMENT = 0x61c88647 HASH_INCREMENT / each time the method is called, the hashcode value increments HASH_INCREMENTprivate static int nextHashCode () {private (HASH_INCREMENT) } / / is used to calculate the subscript value of the array. There are N 1s in table.length-1 to binary, so the value of / / key.threadLocalHashCode & (table.length-1) is the low N-bit int I = key.threadLocalHashCode & (table.length-1) of threadLocalHashCode; the 4.ThreadLocal.set method parses public void set (T value) {/ / gets the current thread Thread t = Thread.currentThread (); / / gets ThreadLocalMap ThreadLocalMap map = getMap (t) based on the current thread / / create one if map is empty, otherwise set the attribute value if (map! = null) / / key to the reference of the current thread, set the value map.set (this, value); if else / / map is empty, create the ThreadMap of the current thread and bind createMap (t, value) to the current thread } 4.1.ThreadLocalMap.set method Analysis private void set (ThreadLocal key, Object value) {/ / assign the initialized current array to the temporary array tab Entry [] tab = table; / / get the current temporary tab array length int len = tab.length; / / calculate the array subscript int I = key.threadLocalHashCode & (len-1) corresponding to the current key / / cycle backwards from the current subscript. If the current array slot is empty, jump out of the loop directly. If it is not empty, then key is judged / / because the structure of ThreadLocalMap is only an array and there is no linked list. When key conflicts and / / different key locates to the same array subscript, the first subscript null / / slot or the first key bit expired key slot will be searched back. And put entry in and assign for (Entry e = tab [I] E! = null; e = tab [I = nextIndex (I, len)]) {/ / the logic in the loop is only obtained when the slot with subscript I is empty; / / CASE1: if the key is the same, replace the value and jump out of the loop if (k = = key) {e.value = value Return;} / / CASE2: if key is empty, the key has expired, and the slot corresponding to the current subscript can be replaced with if (k = = null) {/ / replace the logical replaceStaleEntry (key, value, I) of the expired key; return }} / / if the array slot under the current subscript is empty, occupy the slot and assign tab [I] = new Entry (key, value); / / increasing the array size int sz = + + size; / / No data is cleaned up, and the size size reaches the expansion threshold if (! cleanSomeSlots (I, sz) & & sz > = threshold) rehash ();} 4.2.ThreadLocalMap.replaceStaleEntry method analysis
When finding array slots for the current key, when the key corresponding to the subscript found is the expired key, perform the replace operation
Private void replaceStaleEntry (ThreadLocal key, Object value, int staleSlot) {/ / Array list Entry [] tab = table; / / Array length int len = tab.length; / / temporary variable Entry e; / / the starting subscript of the data to be cleaned. Default is current staleSlot int slotToExpunge = staleSlot. / / look forward from the current staleSlot, find the entry under the corresponding array slot, and exit the loop for until you encounter an empty slot (int I = prevIndex (staleSlot, len); (e = tab [I])! = null (e = tab [I])! = null; I = nextIndex (I, len) {/ / get the key ThreadLocal k = e.get () of the current element; / / if the key is the same, replace value and migrate the data location if (k = = key {e.value = value) / / place the expired tab [staleSlot] on the found I subscript tab [I] = tab [staleSlot]; / / replace the slot under the current staleSlot subscript with the current entry, and the location of the data is optimized by tab [staleSlot] = e / / the expired key if (slotToExpunge = = staleSlot) was not found in the forward process. / / modify the subscript slotToExpunge = I after the starting subscript of the data to be cleaned is replaced; / / Clean the data cleanSomeSlots (expungeStaleEntry (slotToExpunge), len); return } / / k==null indicates that no matching key / / slotToExpunge = = staleSlot was found during the loop, indicating that no expired key if was found during the forward traversal (k==null & & slotToExpunge = = staleSlot) / / you can point the I that the loop looks back to slotToExpunge Because the same key / / was not found in the backward search process. There is no need to deal with slotToExpunge = I during this period. } 4.3.ThreadLocalMap.cleanSomeSlots method analysis
Why is there while ((n > = 1)! = 0)? isn't it possible that you can't clean up all the data? Yes, the design line of ThreadLocal is a partial cleanup, similar to sampling to avoid cleaning up all performance impacts.
Private boolean cleanSomeSlots (int I, int n) {boolean removed = false; Entry [] tab = table; int len = tab.length; do {I = nextIndex (I, len); Entry e = tab [I]; if (e! = null & & e.get () = null) {n = len; removed = true / / perform cleanup, data may be migrated I = expungeStaleEntry (I);}} while ((n > = 1)! = 0); return removed;} 4.4.ThreadLocalMap.rehash expansion operation
Carry out a comprehensive clean-up operation before capacity expansion
Private void rehash () {expungeStaleEntries (); if (size > = threshold-threshold / 4) resize ();}
The expansion logic is relatively simple, the array becomes twice as large, the old data is migrated to the new array, and if the key has expired, set the value to empty directly.
Private void resize () {Entry [] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry [] newTab = new Entry [newLen]; int count = 0; for (int j = 0; j
< oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal k = e.get(); //如果对应的key已经回收 if (k == null) { //value设置为空 e.value = null; // Help the GC } else { //进行数据迁移,如果存在冲突,则放到计算出来的下标的后方第一个不为null的槽 int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } //重新设置扩容阈值 setThreshold(newLen); size = count; table = newTab;}5.ThreadLocal.get方法分析5.1.方法调用过程 当我们调用threadLocal的get方法的时候,首先会调用getMap方法,该方法根据当前线程获取当前线程的ThreadLocal.ThreadLocalMap threadLocals属性,如果非空,再获取对应的ThreadLocal的ThreadLocalMap 里面的entry,根据entry获取对应的value,这个过程会调用expungestaleEntry方法,清空key为空的hash槽的值,并将key不为空的且通过key的hash值计算出来的下标发生过向后偏移的entry移动到更靠近计算出来的下标值的后面的某个空的槽内。如果getMap返回空,说明我们可能没用调用ThreadLocal的set方法的情况下调用了get方法,那么创建一个ThreadLocalMap,初始化entry数组,设置扩容阈值,并设置对应的ThreadLocal的hash槽的值为空。 public T get() { //获取当前线程 Thread t = Thread.currentThread(); //取出当前线程的ThreadLocalMap属性 ThreadLocalMap map = getMap(t); //如果当前线程的ThreadLocalMap不为空 if (map != null) { //获取ThreadLocalMap的Entry数组 ThreadLocalMap.Entry e = map.getEntry(this); //如果数组不为空,取出value值返回 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}5.2.ThreadLocal.getMap方法分析//获取thread的threadLocals属性ThreadLocalMap getMap(Thread t) { return t.threadLocals;}5.3.ThreadLocalMap.getEntry方法分析//获取ThreadLocalMap的entry数组对应下标的数据private Entry getEntry(ThreadLocal key) { //计算下标 int i = key.threadLocalHashCode & (table.length - 1); //获取对应下标数据 Entry e = table[i]; if (e != null && e.get() == key) return e; //如果取不到,为什么有这种情况? //从put方法中我们知道,threadlocalMap不同于hashMap //内部只有数组,数组的每个hash槽下只有一个entry值 //如果在put的时候发现对应hash槽的值不为空,且key不相同 //则往后找第一个为空的hash槽,讲entry放入该hash槽 else return getEntryAfterMiss(key, i, e);}5.4.ThreadLocalMap.getEntryAfterMiss方法分析//从对应下标往后循环查找,这里有个特殊的地方nextIndex//该方法:从对应下标往后循环返回下标,如果超出数组长度,//则从0下标开始继续往后循环返回下标private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //循环遍历 while (e != null) { ThreadLocal k = e.get(); //case1:key值相同,返回对应的entry if (k == key) return e; //case2:发现对应entry数组下标下的key为空,清理 if (k == null) expungeStaleEntry(i); //case3:key不为空但key不相同,数组下标往后推进 else i = nextIndex(i, len); //返回下一个下标值对应的entry e = tab[i]; } return null;}//从对应下标往后循环,如果超出数组长度,则从0下标开始继续往后循环//返回具体下标值private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0);}5.5.ThreadLocalMap.expungeStaleEntry方法分析 从当前staleSlot开始循环清理过期key对应的entry数组内的值;如果key不为空且当前线程对应的threadlocal的hash值计算出来的下标发生过迁移,说明之前在put的时候,在对应下标下发生过hash冲突,将当前下标下的entry数组对应的值置为null,并将当前下标下的entry值移动到更接近通过hash值计算出来的下标之后的某个空的槽中。循环在进行下标右移的过程中,如果碰到对应下标下的槽数据为空,则退出循环。该方法在执行的时候会将本该在staleSlot位置的key对应的变量移动到该位置或更靠近该位置的后方。避免remove方法遍历的时候出现null导致清理不到的情况。 private int expungeStaleEntry(int staleSlot) { //将全局entry数组赋值给临时tab Entry[] tab = table; //临时entry数组当前长度 int len = tab.length; //设置对应数组下标下的entry的value为空 tab[staleSlot].value = null; //设置对应entry为空 tab[staleSlot] = null; //entry数组全局长度-1 size--; Entry e; int i; //从当前下标往后循环遍历,直到对应的下标下槽内数据为空跳出循环 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { //获取对应下标下当前entry对应的key ThreadLocal k = e.get(); //如果key为空则清理entry的value和设置当前数组对应entry为空 if (k == null) { e.value = null; tab[i] = null; size--; //如果key不为空 } else { //计算获取对应的下标,这个本该是存放entry的位置,但是可能由于hash冲突,put的时候向后偏移了 int h = k.threadLocalHashCode & (len - 1); //条件成立说明在put的时候计算出来的下标发生过hash冲突 //数据向后偏移过,而且 h < i if (h != i) { //将当前下标下entry设置为空 tab[i] = null; //从计算出来的下标h循环向后获取一个对应entry为空的下标值 //该下标下存放当前entry while (tab[h] != null) //这个新计算出来的h的值更靠近计算获取的下标 h = nextIndex(h, len); //将entry放在对应下标 tab[h] = e; } } //返回进行处理过后的起点下标i return i;}5.6.ThreadLocal.setInitialValue方法分析private T setInitialValue() { //获取一个空值 T value = initialValue(); Thread t = Thread.currentThread(); //获取当前线程的ThreadMap ThreadLocalMap map = getMap(t); //如果不为空,则将当前空值注入 if (map != null) map.set(this, value); else //否则创建这个ThreadMap并和当前Thread绑定 createMap(t, value); return value;}6.ThreadLocal.remove方法分析 remove方法也很简单,就是将key的引用设置为null,然后找到key所对应的数组槽位,执行清理操作。 在ThreadLocal使用完毕后,执行remove方法防止内存溢出。 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this);}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; } }}public void clear() { this.referent = null;}7.InheritableThreadLocal分析 上面说完了ThreadLocal的问题,可以看出,ThreadLocal只能在单个线程内部传递参数,无法在子父线程间传递参数。 但是InheritableThreadLocal的出现解决了这个问题。 public class InheriTableThreadLocalTest { private static final InheritableThreadLocal threadLocal = new InheritableThreadLocal(); public static void main(String[] args) { threadLocal.set("主线程设置值"); new Thread(() ->{System.out.println (threadLocal.get ();}) .start ();}}
Analyze the InheritableThreadLocal class and find that it inherits from ThreadLocal, but maintains inheritableThreadLocals when createMap,getMap
Public class InheritableThreadLocal extends ThreadLocal {protected T childValue (T parentValue) {return parentValue;} ThreadLocalMap getMap (Thread t) {return t.ThreadLocals;} void createMap (Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap (this, firstValue);}}
In the code init method of thread initialization, there is a piece of logic:
If the inheritThreadLocals of the parent thread is not empty, the ThreadLocal.createInheritedMap method is called, which passes the inheritableThreadLocals of the parent thread
If (inheritThreadLocals & & parent.inheritableThreadLocals! = null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap (parent.inheritableThreadLocals)
Also look at the ThreadLocal.createInheritedMap method. When the child thread is created, it copies the inheritableThreadLocals of the parent thread and saves it in its own inheritableThreadLocals.
Static ThreadLocalMap createInheritedMap (ThreadLocalMap parentMap) {return new ThreadLocalMap (parentMap);} private ThreadLocalMap (ThreadLocalMap parentMap) {Entry [] parentTable = parentMap.table; int len = parentTable.length; setThreshold (len); table = new Entry [len]; for (int j = 0; j < len; jacks +) {Entry e = parentTable [j] If (e! = null) {@ SuppressWarnings ("unchecked") ThreadLocal key = (ThreadLocal) e.get (); if (key! = null) {Object value = key.childValue (e.value); Entry c = new Entry (key, value); int h = key.threadLocalHashCode & (len-1) While (table [h]! = null) h = nextIndex (h, len); table [h] = c; size++;} after reading the above, have you mastered the method of ThreadLocal source code analysis? If you want to learn more skills or want to know more about it, you are welcome to follow the industry information channel, thank you for reading!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.