The full text JUC

ThreadLocal

  • Provides local variables within a thread that do not interfere with each other and only work for the lifetime of the thread, reducing the complexity of passing some common variables between multiple functions or components within the same thread
  • Features, appliedA weak reference
  1. Thread concurrency, used in multi-threaded concurrency scenarios
  2. Pass the data throughThreadLocalDelivered in different components on the same thread
  3. Thread isolation. Each thread variable is independent and does not affect each other

use

  • One thread goes toThreadLocalPut, another thread can not get, has isolation characteristics
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //null
        System.out.println(tl.get());
    }).start();
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tl.set(new Person());
        //com.java.threadlocal.Person@4ebf4ac7
        System.out.println(tl.get());
    }).start();
}
Copy the code

Relationship of parts

  • aThreadThere is aThreadLocalMap
  • aThreadLocalMapContains more than oneEntry to the
  • aEntry to theAs aThreadLocalThe objects andvalueConstitute a
  • aThreadLocalCan be multipleThreadtheThreadLocalMapthekey
  • aThreadYou can only pass your ownThreadLocalMap, according to theThreadLocalGet the correspondingvalue

  • After JDK8, this design approach eachThreadLocalMapStore fewer key-value pairs perThreadMaintain one’s ownThreadLocalMap, aThreadLocalMapThe number of key-value pairs is determined byThreadLocalThe decision, and in real development, not so much, to avoid hash conflicts

In JDK8, ThreadLocal maintains a Map with thread-values as key-value pairs, and the number is determined by the Thread

  • whenThreadAfter the destruction,ThreadLocalMapIt will also be destroyed, reducing memory usage

And sychronized distinction

  • Common, both can be used to deal with the problem of multi-threaded concurrent access to variables
  • sychronizedTime for space, provide only one variable, let different threads queue access, focus onSynchronization of resources accessed by multiple threads
  • ThreadLocalSpace for time, for each thread to provide a thread unique variable, achieve simultaneous access without interfering with each other, focus onData isolation between each thread

Application in Spring transactions

  • Ensure that all operations are in one transaction and that each operation uses the same connection

The connection is the same for the data layer and the service layer

  • In the case of concurrent threads, each thread can only operate on its own connection
  • A common solution that requires the connection to be passed in as a parameter and to be usedsynchronizedEnsuring thread safety

Increase code coupling, affecting performance

The source code examples

  • @TransactionalThe final callDataSourceTransactionManager, the use ofThreadLocalpassconnection
  • doBeginFirst check if there is a connection object, if not, get one, and set tonewConnectionHolder

  • doBeginIt checks if it is a new connection, and if it is passedTransactionSynchronizationManagerwithThreadLocalMapThe binding

  • Set toresourcesIn order tomapType storage,keyIs the data source,valueIs a connection, indicating that one thread, one data source, corresponds to one connection

  • resourcesIn fact, it’s aThreadLocal, the element type inside isMap<Object, Object>

Application in MyBatis

  • On the pagePageHelper, the appropriate paging method is selected based on the current database connection
PageHelper.startPage(2, 1);
List<Account> accounts = accountMapper.findAll();
for (Account account : accounts) {
    System.out.println(account);
}
Copy the code
  • startPage
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); // oldPage <E> oldPage = getLocalPage(); if (oldPage ! = null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } // key setLocalPage(page); return page; }Copy the code
  • setLocalPageTo giveThreadLocalMapSet up thePageobject
protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}
Copy the code

  • PageInterceptorInterceptor, callinterceptTo complete all operation callsafterAll

  • afterAll, will correspond to the current threaddialectandpageClean up,removeoff
public void afterAll() { AbstractHelperDialect delegate = this.autoDialect.getDelegate(); if (delegate ! = null) { delegate.afterAll(); this.autoDialect.clearDelegate(); } clearPage(); }Copy the code
  • getDelegateAnd in fact fromThreadLocalGets the value of the current threadAbstractHelperDialect, applied the proxy mode, and finally byPageHelperTo enhance deletion
public AbstractHelperDialect getDelegate() { return this.delegate ! = null ? this.delegate : (AbstractHelperDialect)this.dialectThreadLocal.get(); }Copy the code
  • clearPagecallremove
public static void clearPage() {
    LOCAL_PAGE.remove();
}
Copy the code

Set

  • The main work
  1. Set the value if noneThradLocalMapJust create it for it
  2. During the actual setup, if foundkIf it is equal, replace it; If you findk==null, carry out a clean-up, and at the same time, if foundkEqual, same substitution, if there is no equal, put findnullThe place where
  • Get the current thread, putvalueIs put into the current threadmapIn,mapthekeyFor the currentThreadLocalobject
Public void set(T value) {// Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! Map.set (this, value); Else create createMap(t, value) if no; }Copy the code
  • getMapCorresponding toThreadMember variable ofthreadLocalsEach time a thread appears, one is initializedThreadLocals of type ThreadLocalMap, specific to the threadmap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code
  • createMapInitializes the current thread corresponding toThreadLocalMap
Void createMap(Thread t, t firstValue) {void createMap(Thread t, t firstValue) {// The current instance of ThreadLocal as the key. firstValue); }Copy the code
  • ThreadLocalMapstructure
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Table is an Entry (inherits WeakReference) array INITIAL_CAPACITY Default 16 // Capacity must be an integer power of 2 table = new Entry[INITIAL_CAPACITY]; / / linear detection method, find a subscript int I = firstKey threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; // Set the capacity expansion threshold. If the threshold is greater than it, setThreshold(INITIAL_CAPACITY) needs to be expanded. }Copy the code
  • setActually carry outsetThe operation corresponding to the current threadmapThe traverse
private void set(ThreadLocal<? > key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); For (Entry e = TAB [I]; e ! = null; I + 1 < len? i + 1 : 0 e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); If (k == key) {// if (k == key) {// if (k == key) {// If (k == key) { return; } if (k == null) {// Find a null key //1. Do a clean up of the whole on the first pass and save the first null to prevent a sudden increase in data //2. The second pass is to see if there is an equal key to the current key. If there is an equal key to the current key, put it at I and null //3. ReplaceStaleEntry (key, value, I) clears all subscripts where 'entry' points to NULL; return; } } tab[i] = new Entry(key, value); int sz = ++size; // Clean up the null element if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

get

  • The main work
  1. To obtainThreadLocalThe value of the instance object, or null if none is present
  2. If the currentThreadThere is noThreadLocalMapCreates for it and willThreadLocal-nulljoin
  3. When searching, the first attempt is a direct hit, if not found, try traversing the search, while cleaningk == nulltheEntry
  • get, the normal writing method
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! Threadlocalmap. Entry e = map.getentry (this); // Get 'Entry' threadLocalMap. Entry e = map.getentry (this); if (e ! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + return result; }} // Null return setInitialValue(); }Copy the code
  • 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; Return getEntryAfterMiss(key, I, e); }Copy the code
  • getEntryAfterMiss, traversal search, while traversal, clear tonulltheEntry
private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e ! = null) {// Return null ThreadLocal<? > k = e.get(); if (k == key) return e; If (k == null) // Delete expungeStaleEntry(I); else i = nextIndex(i, len); e = tab[i]; } return null; }Copy the code
  • setInitialValueTo create a map for it
Private T setInitialValue() {// Return null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! Map. set(this, value); Else // Create a createMap(t, value) for it; // Null return value; }Copy the code
  • initialValue, is actually just a returnnull, can be inheritedThreadLocalOverride this method to return the initial value
protected T initialValue() {
    return null;
}
Copy the code

remove

  • removeThere,mapTo findkeydelete
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m ! = null) m.remove(this); }Copy the code
  • ThreadLocalMaptheremoveTo traverse themapTo delete
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();
            //清理这个Entry的同时,做一次整体清理
            expungeStaleEntry(i);
            return;
        }
    }
}
Copy the code

Entry

  • EntryinheritanceWeakReferenceIt’s actually pointing toA virtual reference to a ThreadLocal instance object
static class Entry extends WeakReference<ThreadLocal<? >> { Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code

Why weak references

  • inset/getIn the method, it’s going to bekfornullJudge and clean up
  • If you use strong references, you do not need to use the currentThreadLocal, the currentThreadLocalIf the instance object is left empty, it can be recycled, but inThreadLocalMapIn theEntryStill pointing to the currentThreadLocal, cannot be recycled, will produceA memory leak, onlyThreadMapCan be recycled, can be recycled
  • If it is a weak reference, willThreadLocalLife cycle andThreadUntying, just need to put the current external useThreadLocalThe instance object is left empty, insideEntryIt points to a weak reference as long asGCIt gets recycled, butEntryIn thevalueStill exists, byEntryObject pointing, which cannot be reclaimed, can also be generatedA memory leak

When the current Entry is not used, tl.remove() is required; , the call to get/set will still remove, but it will not call get/set for a long time

  • When a thread comes from a thread pool,ThreadLocalMapIf it is not cleaned up, it will affect the next use and lead to more and more space

A memory leak

  • True cause andEntryIt doesn’t matter if it’s a weak referenceThreadLocalThere is no timelyremove, resulting inMapMore and more big
  1. No manual deletionEntry
  2. Threads always exist,ThreadLocalMapLife cycle followingThreadThe same
  • Use weak references to avoidThreadLocalMapStill point toThreadLocalCan’t be recycled
  • After use, to timelyremoveTo prevent theEntryPoint to thevalueCan’t be recycled in time

capacity

  • setThreshold, initialized toTwo-thirds of the
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}
Copy the code
  • rehashTo clean up
Private void rehash() {// Remove empty positions expungeStaleEntries(); If (size >= threshold-threshold /4) resize(); // If (size >= threshold-threshold /4) resize(); }Copy the code
  • resizeActually expand it, copy it, double it
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) {// Copy 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; }Copy the code

Hash conflict

  • Due to theThreadLocalMapIt’s a constant process in itselfIterate over remove as a whole, can be combined withOpen address methodTo resolve hash conflicts
  • To solveHash conflictThe core of the
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
Copy the code
  • Relevant part
private final int threadLocalHashCode = nextHashCode(); Private static AtomicInteger nextHashCode = new AtomicInteger(); Private static final int HASH_INCREMENT = 0x61C88647; private static final int HASH_INCREMENT = 0x61C88647; // private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }Copy the code
  • Equivalent to modulo operations,hashcode % size
& (INITIAL_CAPACITY - 1)
Copy the code
  • Linear detection methodnextIndex ((i + 1 < len) ? i + 1 : 0);
  1. Probe the next address once and insert the empty address after knowing it. If the empty address cannot be found in the whole space, it will overflow
  2. If the current length is 16, the calculated I is 14, thentab[14]Has a value on, andkeyIt’s not equal. It happenshashConflict, then+ 1Find the next one and loop through it