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, applied
A weak reference
- Thread concurrency, used in multi-threaded concurrency scenarios
- Pass the data through
ThreadLocal
Delivered in different components on the same thread- Thread isolation. Each thread variable is independent and does not affect each other
use
- One thread goes to
ThreadLocal
Put, 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
- a
Thread
There is aThreadLocalMap
- a
ThreadLocalMap
Contains more than oneEntry to the
- a
Entry to the
As aThreadLocal
The objects andvalue
Constitute a - a
ThreadLocal
Can be multipleThread
theThreadLocalMap
thekey
- a
Thread
You can only pass your ownThreadLocalMap
, according to theThreadLocal
Get the correspondingvalue
- After JDK8, this design approach each
ThreadLocalMap
Store fewer key-value pairs perThread
Maintain one’s ownThreadLocalMap
, aThreadLocalMap
The number of key-value pairs is determined byThreadLocal
The 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
- when
Thread
After the destruction,ThreadLocalMap
It 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
sychronized
Time for space, provide only one variable, let different threads queue access, focus onSynchronization of resources accessed by multiple threads
ThreadLocal
Space 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 used
synchronized
Ensuring thread safety
Increase code coupling, affecting performance
The source code examples
@Transactional
The final callDataSourceTransactionManager
, the use ofThreadLocal
passconnection
doBegin
First check if there is a connection object, if not, get one, and set tonewConnectionHolder
doBegin
It checks if it is a new connection, and if it is passedTransactionSynchronizationManager
withThreadLocalMap
The binding
- Set to
resources
In order tomap
Type storage,key
Is the data source,value
Is a connection, indicating that one thread, one data source, corresponds to one connection
resources
In fact, it’s aThreadLocal
, the element type inside isMap<Object, Object>
Application in MyBatis
- On the page
PageHelper
, 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
setLocalPage
To giveThreadLocalMap
Set up thePage
object
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
Copy the code
PageInterceptor
Interceptor, callintercept
To complete all operation callsafterAll
afterAll
, will correspond to the current threaddialect
andpage
Clean up,remove
off
public void afterAll() { AbstractHelperDialect delegate = this.autoDialect.getDelegate(); if (delegate ! = null) { delegate.afterAll(); this.autoDialect.clearDelegate(); } clearPage(); }Copy the code
getDelegate
And in fact fromThreadLocal
Gets the value of the current threadAbstractHelperDialect
, applied the proxy mode, and finally byPageHelper
To enhance deletion
public AbstractHelperDialect getDelegate() { return this.delegate ! = null ? this.delegate : (AbstractHelperDialect)this.dialectThreadLocal.get(); }Copy the code
clearPage
callremove
public static void clearPage() {
LOCAL_PAGE.remove();
}
Copy the code
Set
- The main work
- Set the value if none
ThradLocalMap
Just create it for it- During the actual setup, if found
k
If it is equal, replace it; If you findk==null
, carry out a clean-up, and at the same time, if foundk
Equal, same substitution, if there is no equal, put findnull
The place where
- Get the current thread, put
value
Is put into the current threadmap
In,map
thekey
For the currentThreadLocal
object
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
getMap
Corresponding toThread
Member variable ofthreadLocals
Each time a thread appears, one is initializedThreadLocals of type ThreadLocalMap
, specific to the threadmap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
createMap
Initializes 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
ThreadLocalMap
structure
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
set
Actually carry outset
The operation corresponding to the current threadmap
The 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
- To obtain
ThreadLocal
The value of the instance object, or null if none is present- If the current
Thread
There is noThreadLocalMap
Creates for it and willThreadLocal-null
join- When searching, the first attempt is a direct hit, if not found, try traversing the search, while cleaning
k == null
theEntry
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 tonull
theEntry
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
setInitialValue
To 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 inheritedThreadLocal
Override this method to return the initial value
protected T initialValue() {
return null;
}
Copy the code
remove
remove
There,map
To findkey
delete
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m ! = null) m.remove(this); }Copy the code
ThreadLocalMap
theremove
To traverse themap
To 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
Entry
inheritanceWeakReference
It’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
- in
set/get
In the method, it’s going to bek
fornull
Judge and clean up - If you use strong references, you do not need to use the current
ThreadLocal
, the currentThreadLocal
If the instance object is left empty, it can be recycled, but inThreadLocalMap
In theEntry
Still pointing to the currentThreadLocal
, cannot be recycled, will produceA memory leak
, onlyThreadMap
Can be recycled, can be recycled - If it is a weak reference, will
ThreadLocal
Life cycle andThread
Untying, just need to put the current external useThreadLocal
The instance object is left empty, insideEntry
It points to a weak reference as long asGC
It gets recycled, butEntry
In thevalue
Still exists, byEntry
Object 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,
ThreadLocalMap
If it is not cleaned up, it will affect the next use and lead to more and more space
A memory leak
- True cause and
Entry
It doesn’t matter if it’s a weak referenceThreadLocal
There is no timelyremove
, resulting inMap
More and more big
- No manual deletion
Entry
- Threads always exist,
ThreadLocalMap
Life cycle followingThread
The same
- Use weak references to avoid
ThreadLocalMap
Still point toThreadLocal
Can’t be recycled - After use, to timely
remove
To prevent theEntry
Point to thevalue
Can’t be recycled in time
capacity
setThreshold
, initialized toTwo-thirds of the
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
Copy the code
rehash
To clean up
Private void rehash() {// Remove empty positions expungeStaleEntries(); If (size >= threshold-threshold /4) resize(); // If (size >= threshold-threshold /4) resize(); }Copy the code
resize
Actually 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 the
ThreadLocalMap
It’s a constant process in itselfIterate over remove as a whole
, can be combined withOpen address method
To resolve hash conflicts - To solve
Hash conflict
The 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 method
nextIndex ((i + 1 < len) ? i + 1 : 0);
- 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
- If the current length is 16, the calculated I is 14, then
tab[14]
Has a value on, andkey
It’s not equal. It happenshash
Conflict, then+ 1
Find the next one and loop through it