introduce
Let’s take a look at the official description of the JDK documentation: The ThreadLocal class provides local variables that can be accessed in a multithreaded environment (get,set) to ensure that the variables of each thread are relatively independent of the variables of other threads. ThreadLocal instances are generally private static. Context used to associate threads. ThreadLocal: Provides local variables within a thread that are not interfered with by different threads. This variable can be used for the lifetime of the thread and can reduce the complexity of passing common variables between functions or components within the same thread.
Thread concurrency: used in a multi-threaded concurrency environment
Pass data: Pass public variables in different components on the same thread via ThrealLocal.
Thread isolation: Variables within each thread are independent and do not affect each other.
A preliminary study to use
ThreadLocal maintains a HashMap where key = the current thread and value = local variables bound to the current thread.
Use ThreadLocal
-
First, there is no
public class UserThreadLocal { private String str = “”; public String getStr() {return str; } public void setStr(String j) {this.str = j; } public static void main(String[] args) { UserThreadLocal userThreadLocal = new UserThreadLocal(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { Userthreadlocal.setstr (thread.currentThread ().getName() + “data “); Thread.out.println (thread.currentThread ().getName() + “id” + userThreadLocal.getstr ()); }}); Thread. setName(” thread “+ I); thread.start(); }}}
The following result is displayed if the command is repeated several times:
2. Use Synchronized
Synchronized (userThreadLocal.class) {userThreadLocal.setstr (thread.currentThread ().getName() +) "Data "); Thread.out.println (thread.currentThread ().getName() + "id" + userThreadLocal.getstr ()); }}Copy the code
The result is always correct if executed several times:
3. Use the ThreadLocal
public class UserThreadLocal { static ThreadLocal<String> str = new ThreadLocal<>(); public String getStr() {return str.get(); } public void setStr(String j) {str.set(j); } public static void main(String[] args) { UserThreadLocal userThreadLocal = new UserThreadLocal(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { Userthreadlocal.setstr (thread.currentThread ().getName() + "data "); Thread.out.println (thread.currentThread ().getName() + "id" + userThreadLocal.getstr ()); }}); Thread. setName(" thread "+ I); thread.start(); }}}Copy the code
The result is as follows:
Conclusion: Multiple threads assigning attributes to the same shared variable at the same time can cause synchronization and data clutter. Locking can be implemented using synchronization now, and can be implemented using ThreadLocal. Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF
Use again
The database transfer system must ensure that the transfer and transfer have transactional, JDBC API about transactions.
Code implementation
To analyze the transfer business, we first divided the business into four layers.
-
Dao layer: Connect to the database for DATABASE CRUD.
public class AccountDao { public void out(String outUser, int money) throws SQLException { String sql = “update account set money = money – ? where name = ?” ; Connection conn = JdbcUtils.getConnection(); PreparedStatement = conn. PreparedStatement (SQL); PreparedStatement = conn. PreparedStatement (SQL); preparedStatement.setInt(1, money); preparedStatement.setString(2, outUser); preparedStatement.executeUpdate(); JdbcUtils.release(preparedStatement, conn); }
public void in(String inUser, int money) throws SQLException { String sql = "update account set money = money + ? where name = ?" ; Connection conn = JdbcUtils.getConnection(); PreparedStatement = conn. PreparedStatement (SQL); preparedStatement.setInt(1, money); preparedStatement.setString(2, inUser); preparedStatement.executeUpdate(); JdbcUtils.release(preparedStatement, conn); }Copy the code
}
-
Service layer: open and close transactions and invoke the DAO layer.
public class AccountService { public boolean transfer(String outUser, String inUser, int money) { AccountDao ad = new AccountDao(); // service call DAO layer Connection conn = null; Conn = jdbCutils.getConnection (); conn = jdbCutils.getConnection (); // The database connection pool obtains conn.setautoCommit (false); // Turn off auto commit
ad.out(outUser, money); Int I = 1/0; // An exception is deliberately used to check the transactional nature of the database. ad.in(inUser, money); JdbcUtils.commitAndClose(conn); } catch (SQLException e) {e.printstackTrace (); JdbcUtils.rollbackAndClose(conn); Return false; } return true; }Copy the code
}
-
Utils layer: closure and retrieval of database connection pools.
public class JdbcUtils { private static final ComboBoxPopupControl ds = new ComboPooledDataSource(); public static Connection getConnection() throws SQLException { return ds.getConnection(); } public static void release(AutoCloseable… ios) { for (AutoCloseable io : ios) { if (io ! = null) { try { io.close(); } catch (Exception e) { e.printStackTrace(); }}}} public static void commitAndClose(Connection conn) {try {// commitAndClose if (conn! = null) { conn.commit(); conn.close(); } } catch (SQLException e) { e.printStackTrace(); }}
Public static void rollbackAndClose(Connection conn){try{// If (conn! =null){ conn.rollback(); conn.close(); } }catch (SQLException e){ e.printStackTrace(); }}Copy the code
}
-
Web layer: The real call portal.
public class AccountWeb { public static void main(String[] args) { String outUser = “SoWhat”; String inUser = “wheat “; int money = 100; AccountService as = new AccountService(); boolean result = as.transfer(outUser,inUser,money); If (result == false){system.out.println (” transfer failed “); } else{system.out.println (” transfer successful “); }}}
Pay attention to the point
- To ensure that all operations are in a transaction, the case connections must be the same. The connection used by the Service layer to start a transaction must be the same as the connection used by the DAO layer to access the database.
- In the case of concurrent threads, each thread can only operate on its own connection. The above considerations are reflected in the code: the service layer obtains connections and opens transactions the same as the DAO layer connections, and the thread can only operate its own connections at the current time.
Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF
Unusual way of thinking
Pass: Pass the Service layer Connection object directly to the DAO layer.
The general locking code changes as follows:
Disadvantages:
Improve code coupling: The Service layer Connection object is passed to the DAO layer.
Reduced application performance: Because locking reduces system performance.
Spring uses Threadlocal to ensure that database operations in a single thread use the same database connection. At the same time, in this way, the business layer does not need to sense and manage connection objects when using transactions. Switching between multiple transaction configurations can be cleverly managed through propagation level. Suspend and resume.
ThreadLocal ideas
With ThreadLocal, the core idea is that the service and the DAO connect from the database to ensure that the same.
Utils modifies part of the code as follows:
static ThreadLocal<Connection> tl = new ThreadLocal<>(); private static final ComboBoxPopupControl ds = new ComboPooledDataSource(); public static Connection getConnection() throws SQLException { Connection conn = tl.get(); if (conn == null) { conn = ds.getConnection(); tl.set(conn); } return conn; } public static void commitAndClose(Connection conn) { try { if (conn ! = null) { conn.commit(); tl.remove(); Conn.close (); conn.close(); } } catch (SQLException e) { e.printStackTrace(); }}Copy the code
ThreadLocal advantage:
1. Data transfer: Save the data bound by each thread and get it directly where it is needed to avoid the coupling caused by parameter transfer. 2. Thread isolation: Data between threads is isolated and concurrent, avoiding performance loss caused by synchronous locking.
The underlying
misunderstanding
Without looking at the source code and just thinking about it from the perspective of what we’ve been told to use: a shared Map where each child thread =Key corresponds to a stored ThreadLocal Value =Value. In the early days of the JDK, this was indeed the case, but not now!
JDK8 in design
The design of ThreadLocal in JDK8 is as follows: Each Thread maintains a Map whose key is the ThreadLocal object and whose value is the real object.
Each Thread has an internal Map(ThreadLocalMap). A Thread can have multiple TreadLocal objects for different types of objects, but they will be placed in the ThreadLocalMap of your current Thread, so you must use an array to store them.
The Map stores ThreadLocal objects as keys and copies of thread variables as values.
The Map inside a Thread is maintained by the ThreadLocal class, which is responsible for fetching and setting Thread variable values from the Map.
When different threads obtain duplicate values each time, other threads cannot obtain duplicate values of the current thread, forming duplicate isolation and mutual interference.
advantage
The JDK8 design has an advantage over the earlier JDK design. The main change we can see is that threads and ThreadLocal are switched.
ThreadLocal maintains a ThreadLocalMap with threads as keys in the map. New in: Thread maintains a ThreadLocalMap with the current ThreadLocal as the key.
- The KV data stored by each Map becomes smaller, and the number of KV stored by ThreadLocal increases when the number of threads is larger. K now uses a ThreadLocal instance as the key. In multithreading, ThreadLocal instances are usually fewer than threads.
- ThreadLocalMap is now destroyed when a Thread is destroyed, reducing memory usage.
Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF
ThreadLocal core method
ThreadLocal can be exposed in four ways:
Set method
Public void set(T value) {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! Set (this, value); Call map.set to assign the current value to the current threadLocal. else createMap(t, value); // If the current object has no ThreadLocalMap object. ThreadLocalMap ThreadLocalMap getMap(Thread t) {return t.htreadlocals; } // Create a threadlocals void createMap(Thread t, t firstValue) {t.htreadlocals = new ThreadLocalMap(this, firstValue); }Copy the code
Execution process:
Get the current thread and get the map based on the current thread.
If the map is not empty, set the parameter to the map, currently using Threadlocal as the key.
If the map is empty, create a map for the thread and set the initial value.
The get method
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // Thread object corresponding map if (map! = null) { ThreadLocalMap.Entry e = map.getEntry(this); // Try to get the entity if (e! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; }} // If the map of the current thread does not exist // If the map exists but the current threadLocal has no associated entry. return setInitialValue(); } // Initialize private T setInitialValue() {T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); return value; }Copy the code
First try to get the current thread, get the map corresponding to the current thread.
If the map obtained is not empty, try to obtain an entry using the current ThreadLocal as the key.
If entry is not empty, return the value.
If 2 and 3 are not available, use the initialValue function to get the initialValue, and then create a new map for the current thread.
remove
First try to get the current thread, then get the map based on the current thread, and try to remove enrty from the map.
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m ! = null) m.remove(this); }Copy the code
initialValue
-
If you get directly without calling set, this method is called, and it is called only once,
-
Return a default value of null.
-
If you do not want to return NULL, Override can Override it.
protected T initialValue() { return null; }
ThreadLocalMap source code analysis
When analyzing important ThreadLocal methods, you can see that the operations of ThreadLocal are all around ThreadLocalMap, where 2 contains 3 and 1 contains 2.
public class ThreadLocal
static class ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<? >>
ThreadLocalMap member variable
The same parameters as HashMap are not repeated here.
Private static final int INITIAL_CAPACITY = 16; private Entry[] table; private int size = 0; private int threshold; // Default to 0Copy the code
ThreadLocalMap main function
Some of the get, set, and remove methods in ThreadLocal call these functions
set(ThreadLocal,Object)
remove(ThreadLocal)
getEntry(ThreadLocal)
Copy the code
The inner class Entry
// Entry inherits WeakReference, and the key must say ThreadLocal // If the key is null, that means the key is no longer referenced, Static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code
In ThreadLocalMap, Entry is used to save KV structure, and key(Threadlocal) in Entry is a weak reference, which aims to unbind the life cycle of Threadlocal object from the cycle of thread. Weak references:
WeakReference: Something useful (less than a soft reference) but not necessary. Objects associated with weak references will only survive until the next garbage collection. When GC occurs, they will be reclaimed regardless of memory.
Weak references and memory leaks
Some people might think that memory leaks with ThreadLocal are related to the use of weak reference keys in Entry, but that’s not true.
If Key is a strong reference
If ThreadLocal is used up in the business code, the ThreadLocalRef in the Stack will be reclaimed.
However, the Key in the Entry of the ThreadLocalMap strongly references ThreadLocal, causing the ThreadLocal instance to fail to be reclaimed.
If we do not delete an Entry and CurrentThread is still running, the strong reference chain in red will cause an Entry memory leak.
Conclusion: Strong references do not prevent memory leaks.
If key is a weak reference
If ThreadLocal is used up in the business code, the ThreadLocalRef in the Stack will be reclaimed.
However, in this case, the Entry Key in the ThreadLocalMap is a weak reference to ThreadLocal, which results in the actual reclamation of ThreadLocal. In this case, the Entry Key is null.
However, when we do not manually delete Entry and CurrentThread is still running, we still have a strong reference chain, because ThreadLocalRef has been reclaimed, and the value cannot be accessed, resulting in a value memory leak!
Bottom line: Weak references are not immune to memory leaks. Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF
Cause of memory leak
After the above analysis, we know that memory leaks are independent of strong/weak applications. There are two preconditions for memory leaks.
Entries are not manually deleted after ThreadLocalRef runs out.
CurrentThread is still running ing after ThreadLocalRef runs out.
- The first point shows that when ThreadLocal is used, memory leaks can be avoided by calling its corresponding remove method to remove the corresponding Entry.
- The second point is that since ThreadLocalMap is a property of CurrentThread, it is referenced by the CurrentThread and has the same lifecycle as CurrentThread. If ThreadLocalMap is reclaimed when the CurrentThread terminates, the Entry in it will be reclaimed as well. The only problem is that if the thread is not the same, it will be recycled. If it’s a thread pool, it goes back to the pool.
Conclusion: The root cause of a ThreadLocalMap memory leak is that a ThreadLocalMap has the same life cycle as a Thread.
Why weak references
Memory leaks are independent of strong and weak references, so why weak references? We know there are two ways to avoid memory leaks.
After ThreadLocal is used, it calls the remove method to remove the corresponding Entry.
When ThreadLocal ends, the current Thread ends.
The first method is easy to achieve, the second stop is not easy to do ah! Especially if the thread is taken from the thread pool and then put back into the pool, it will not be destroyed.
In ThreadLocalMap, the set/getEntry method determines that key = NULL (ThreadLocal = null). If key = null, the value will be set to NULL.
This means that when we’re done using ThreadLocal and the Thread is still running even if we forget to call remove, the weak reference will have one more layer of protection than the strong reference, the weak reference ThreadLocal will be withdrawn and the key will be null, The corresponding value will be called the next time we call any of ThreadLocal’s set/get/remove methods in the underlying ThreadLocalMap. Useless values are removed to avoid memory leaks. The corresponding function is expungeStaleEntry.
Hash conflict constructor
Let’s look at the ThreadLocalMap constructor:
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; . / / the new table int I = firstKey threadLocalHashCode & (INITIAL_CAPACITY - 1); Table [I] = new Entry(firstKey, firstValue); // Place a new entry size = 1; SetThreshold (INITIAL_CAPACITY); } threadLocalHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; // Avoid hash collisions as much as possibleCopy the code
In fact, constructors and bit-detail operations see HashMap, write no repeat.
Set method
The process is roughly as follows:
Obtain the corresponding index I according to the key and search for the Entry at the position of I
If the Entry already exists and the key is equal, the value is overwritten.
If an Entry exists but the key is empty, call replaceStaleEntry to replace the Entry with an empty key
If table[I] is null, create a new Entry in table[I] and insert it with size+1.
Call cleanSomeSlots to clean up the Entry with a null key and rehash.
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; E = TAB [I = nextIndex(I, len)]) {ThreadLocal<? > k = e.get(); If (k == key) {// override e.value = value; return; } if (k == null) {// If key is not empty and value is empty, garbage removal memory leak prevention. replaceStaleEntry(key, value, i); return; TAB [I] = new Entry(key, value); // If ThreadLocal's key does not exist and the old element is not found, create a new Entry(key, value) on the empty element. int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int I, int len) {return ((I + 1 < len)? i + 1 : 0); }Copy the code
PS:
Each ThreadLocal can hold only one copy of a variable, and if you want a thread to hold more than one copy, you need to create multiple ThreadLocal.
The internal ThreadLocalMap key is a weak reference and runs the risk of memory leaks.
It applies to high concurrency scenarios where stateless replica variables are independent and business logic is not affected. If the business logic is heavily dependent on replica variables, then ThreadLocal is not the solution and you need to find another solution
What if you want to share ThreadLocal data for a thread?
Use InheritableThreadLocal to create an instance of InheritableThreadLocal that can be accessed by multiple threads. We then get the value set by this InheritableThreadLocal instance in the child thread.
private void test() { final ThreadLocal threadLocal = new InheritableThreadLocal(); Threadlocal.set (" handsome "); Thread t = new Thread() { @Override public void run() { super.run(); Log. I (" threadlocal.get () "); }}; t.start(); }Copy the code
ThreadLocal Static ThreadLocal Static ThreadLocal
Ali specification has a cloud:
ThreadLocal does not solve the problem of updating shared objects. Static decoration is recommended for ThreadLocal objects. This variable is shared by all operations within a thread, so it is set to static. All instances of this class share this static variable, which means that the class is loaded when it is first used, only one piece of storage is allocated, and all objects of this class (as long as they are defined within this thread) can manipulate this variable.
The official JDK specification has the cloud:
The last
Thank you for reading here, the article is inadequate, welcome to point out; If you think it’s good, give me a thumbs up.
Also welcome to pay attention to my public number: programmer Maidong, Maidong will share Java related technical articles or industry information every day, welcome to pay attention to and forward the article!