Detailed ThreadLocal principle and usage

background

I have been on the use of ThreadLocal ambiguous, do not know how to carefully study today to share with you the next.

  • 1, before explaining the first review what ThreadLocal modulo, x ^ y, weak references.

1. actually calculate the modulo operation is the remainder after dividing the two.

Assume that a quotient is divided by b, c, d is a number corresponding to the remainder, almost all computer systems that satisfy a = c * b + d. Therefore, d = ab * c.
 10 17% The results are as follows: d = (17) - ( 17/10) x 10 = (17) - (1 x 10) = 7
 The results of the following 10 to 17%: d = (-17) - ( -17 / 10) x 10 = ( -17) - (-1 x 10) = -7
calculation -17% -10 follows: d = 17 - (17 / -10) x (-10) = (17 ) - (-1 x -10) = 7
the results of 17% -10 follows: d = (-17) - ( -17 / -10) x (-10) = (-17) - (1 x - 10) = -7
can be seen: the symbol and the operation result is always consistent symbols modulus.

2. x ^ y bitwise XOR.

Such as: x is a binary number is a binary number 0101 y 1011 as a result of x ^ y = 1110,0 ^ 1 = 1,0 ^ 0 = 0,1 ^ 1 = 0,1 ^ 0 = 1! As long as there is a value of 1 to 1.

3. Weak references

Weak references are used to describe non-essential objects, garbage collection when the JVM, regardless of whether sufficient memory, the recovery will be a weak object reference associated. In java, represented by java.lang.ref.WeakReference class. Here the objects are associated with weak reference means that only a weak reference associated therewith, while if there is a strong reference associated therewith, garbage collection is performed when the object is not recovered.

  • 2, we have to ask two questions

1. What is the ThreadLocal
ThreadLocal can be understood from the literal sense of thread local variables. This means that if the definition of a ThreadLocal, ThreadLocal each thread to read and write in this thread is isolated from each other does not affect. It provides a variable data each thread has its own independent copy of the threads in order to achieve a closed system.
2. The idea is to achieve what
Tread class has an instance variable of type threadLocals ThreadLocal.ThreadLocalMap we use when there is a thread of its own ThreadLocalMap. ThreadLocalMap has its own independent implementation can be seen as simply that ThreadLocal key, value is placed in the code values (in fact key is not ThreadLocal itself, through the source code may know that it is a weak reference). ThreadLocal set method calls that they will keep to ThreadLocalMap inside. ThreadLocal call the get method when, in their own map inside to find key, in order to achieve a thread isolation.

  • 3, ThreadLocal most important realization is that ThreadLocalMap inside this inner class, we focus on the use of ThreadLocalMap this class to see two masters Josh Bloch and Doug Lea is what design such a good class.

  • 4、ThreadLocalMap

 
image.png

 

All of the above is ThreadLocalMap API
ThreadLocalMap ThreadLocal to provide a customized efficient implementation, and comes with one kind of garbage collection mechanism based on weak references.

  1. Storage structure of
    the storage structure can be understood as a map, but do not be confused and java.util.Map.
 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 

Entry As can be seen from the above code is defined in ThreadLocalMap node, it inherits WeakReference class that defines a value of type Object, a value stored for this ThreadLocal stuffed, key can be treated as ThreadLocal.

  1. Why use weak references, because if there use common form of key-value storage structure is defined, in essence, it will cause a life-cycle node with strong binding thread, as long as the thread is not destroyed, then the node in the GC analysis has been in the can of state, it can not be recycled, but the program itself can not determine whether you can clean node. Weak references are cited four kinds of Java third (the other three strong references, soft references, phantom references), more weaker than the soft references, if an object is not strong reference chain up, then generally survive the next GC . When a ThreadLocal has no strong references unreachable, as it is garbage, Entry keys in ThreadLocalMap in the corresponding fail, which provides a convenient garbage ThreadLocalMap itself.
  2. Entry inside the member variables and methods
       /**
         * 初始容量,必须为2的幂.
         */
        private static final int INITIAL_CAPACITY = 16; /** * 根据需要调整大小。 * 长度必须总是2的幂。 */ private Entry[] table; /** * 表中条目的数量。 */ private int size = 0; /** * 要调整大小的下一个大小值。默认为0 */ private int threshold; // Default to 0 /** * 将调整大小阈值设置维持最坏2/3的负载因子。 */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** *上一个索引 */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * 下一个索引 */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } 

Since ThreadLocalMap using a linear method to resolve hash collision detection, so in fact the Entry [] array in the program logic is present as a ring.

  1. Constructor
         /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         * 构造一个最初包含(firstKey, firstValue)的新映射threadlocalmap是延迟构造的,因      
         *  此当我们至少有一个元素可以放进去的时候才去创建。
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } 

This focus on that hash function & firstKey.threadLocalHashCode = I int (INITIAL_CAPACITY -. 1);
ThreadLocal class has a final modification is of type int threadLocalHashCode, when it generates the ThreadLocal configured, equivalent to a ThreadLocal the ID, which value is derived from

    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode = new AtomicInteger(); /** * 连续生成的哈希码之间的区别——循环隐式顺序线程本地id以近乎最优的方式展开 * 用于两倍大小表的乘法哈希值。 */ private static final int HASH_INCREMENT = 0x61c88647; /** * 返回下一个hashcode */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } 

Calculated on theory and practice, when we use a magic number accumulation 0x61c88647 ThreadLocal each assigned a respective ID and then power is threadLocalHashCode modulo 2, the results obtained is evenly distributed.

ThreadLocalMap linear detection method is used, a uniform distribution of the benefits that can detect quickly available to the next adjacent slot, in order to ensure efficiency. This is why the answer to the size of a power of two problems thrown above. In order to optimize efficiency. For & (INITIAL_CAPACITY - 1), I believe there have been more algorithms to read the source code programmer, one can understand, for a power of 2 modulo a modulus, can & (2n-1) instead of% 2n, bit operations much higher efficiency than the modulus. As for why, because modulo 2 ^ n, if not lower n bits are clearly contributed to the result of 0, it will affect the outcome can only be lower n bits.
  1. TreadLocal get method of
    the get method of ThreadLocal calls this method ThreadLocalMap in getEntry,
      private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } 

This note above it is clear that there is not much talked about in

Simple to say when calling the get method when the situation encountered

The threadLocalHashCode threadLocal into the reference table to obtain the modulo capacity index corresponding to the index if the slot is to be read threadLocal, return the results directly
calling getEntryAfterMiss linear probing, during each encounter invalid slot, calls for expungeStaleEntry cleaning section; if found key, the result is returned entry
did not find the key, or null

  1. The method set TreadLocal
    /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * Replace a stale entry encountered during a set operation * with an entry for the specified key. The value passed in * the value parameter is stored in the entry, whether or not * an entry already exists for the specified key. * * As a side effect, this method expunges all stale entries in the * "run" containing the stale entry. (A run is a sequence of entries * between two null slots.) * * @param key the key * @param value the value to be associated with key * @param staleSlot index of the first stale entry encountered while * searching for key. */ private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * Heuristically scan some cells looking for stale entries. * This is invoked when either a new element is added, or * another stale one has been expunged. It performs a * logarithmic number of scans, as a balance between no * scanning (fast but retains garbage) and a number of scans * proportional to number of elements, that would find all * garbage but would cause some insertions to take O(n) time. * * @param i a position known NOT to hold a stale entry. The * scan starts at the element after i. * * @param n scan control: {@code log2(n)} cells are scanned, * unless a stale entry is found, in which case * {@code log2(table.length)-1} additional cells are scanned. * When called from insertions, this parameter is the number * of elements, but when from replaceStaleEntry, it is the * table length. (Note: all this could be changed to be either * more or less aggressive by weighting n instead of just * using straight log n. But this version is simple, fast, and * seems to work well.) * * @return true if any stale entries have been removed. */ 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; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; } private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } /** * Double the capacity of the table. */ 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(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } /** * Expunge all stale entries in the table. */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } 
Simply summarize the above usage

a, the detection process is not invalid slot, and successfully found where the key slot, a direct replacement to
b, the detection process were found to have invalid slot, call replaceStaleEntry, the ultimate effect will certainly be key and value placed in this slot, and It will clean up as invalid slot
in replaceStaleEntry process, if the find key, then do a swap put it in that slot is invalid, value set to the new value
in replaceStaleEntry process, did not find the key, directly put in place an invalid slot entry
C, the probe found no key, then put a null entry position after the end of the consecutive segments, which is part of the linear detection method. Bled off, do a heuristic clean up, if not cleared out key, and the current table size threshold has been exceeded, and then do a rehash, rehash function is called once the full amount of clean-up slot method also expungeStaleEntries, then if the finished table size exceeds the threshold - threshold / 4, the expansion is performed twice
6, remove the call method ThreadLocal remove in TreadLocalMap

 /**
         * Remove the entry for key.
         */
        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; } } } 

This method is relatively simple, find the key to Clear

  1. ThreadLocal will not encounter a memory leak issue
    regarding memory leaks because there are threads in the thread pool as multiplexing scenario, a thread of life cycle is very long, large objects are not long-term recovery of operating efficiency and safety impacts system. If the thread does not reuse, run that is destroyed there will not be problems caused ThreadLocal memory leaks.
    ThreadLocalMap carefully read the source code, it can be inferred that if the process of using ThreadLocal, the habit plus remove, this will not cause a memory leak.
    If not remove it? If, after the corresponding ThreadLocal thread calls the get and set methods have a high probability will be the way to clean out invalid object off strong reference value, so that large objects are recovered collector.
    We should consider when to call a method to remove the ThreadLocal. A more familiar scene is a request for a thread of the server, such as tomcat, in the code for a section of the web api, to store some user information such as user names, etc., after the connection point method to explicitly call remove.

These are theoretical, we do a little experiment

/**
 * @author shuliangzhao
 * @Title: ThreadLocalDemo
 * @ProjectName design-parent
 * @Description: TODO * @date 2019/6/1 0:00 */ public class ThreadLocalDemo { private static ThreadLocal<ThreadLocalDemo> t = new ThreadLocal<>(); private ThreadLocalDemo() {} public static ThreadLocalDemo getInstance() { ThreadLocalDemo threadLocalDemo = ThreadLocalDemo.t.get(); if (null == threadLocalDemo) { threadLocalDemo = new ThreadLocalDemo(); t.set(threadLocalDemo); } return threadLocalDemo; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public static void main(String[] args) { long i = (1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1)); System.out.println(i); int i1 = 55&(2^2-1); System.out.println(55%2^2); System.out.println(i1); } } 
/**
 * @author shuliangzhao
 * @Title: ThreadLocalTest
 * @ProjectName design-parent
 * @Description: TODO * @date 2019/6/1 0:03 */ public class ThreadLocalTest { public static void main(String[] args) { for(int i=0; i<2;i++){ new Thread(new Runnable() { @Override public void run() { Double d = Math.random()*10; ThreadLocalDemo.getInstance().setName("name "+d); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ System.out.println(ThreadLocalDemo.getInstance().getName()); } } static class B{ public void get(){ System.out.println(ThreadLocalDemo.getInstance().getName()); } } } 

operation result

 

 
image.png

Here we put ThreadLocal finished, you can see more of the source code to see large cattle is how to design such a beautiful code.

Guess you like

Origin www.cnblogs.com/treeshu/p/10959611.html