Concurrent Programming in Java: An in-depth look at ThreadLocal
Let’s take a look at how ThreadLocal works and how it works. First of all, this paper first talks about the understanding of ThreadLocal, then according to the source code of ThreadLocal class analysis of its implementation principle and use need to pay attention to, and finally gives two application scenarios.
The following is an outline of the table of contents:
Understanding ThreadLocal
Parsing the ThreadLocal class in depth
3. Application scenarios of ThreadLocal
Please forgive me if there is anything wrong, and welcome criticism and correction.
Please respect the author’s labor achievements, please mark the link of the original text:
www.cnblogs.com/dolphin0520…
Understanding ThreadLocal
ThreadLocal, in many places it’s called thread-local variables, in some places it’s called thread-local storage, which actually means the same thing. As many of you probably know, ThreadLocal creates a copy of a variable in each thread, so each thread can access its own internal copy of the variable.
This sentence seems easy to understand on the face of it, but it is not so easy to understand.
Let’s start with an example:
?
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class ConnectionManager { private static Connection connect = null ; public static Connection openConnection() { if (connect == null ) { connect = DriverManager.getConnection(); } return connect; } public static void closeConnection() { if (connect! = null ) connect.close(); } } |
Given such a database link management class, this code can be used in a single thread without any problems, but what about in multiple threads? Obviously, there are thread-safety issues associated with using multiple threads: first, neither of the above methods is synchronized, and it is likely that connect will be created multiple times in the openConnection method; Second, since connect is a shared variable, synchronization must be used where connect is called to ensure thread-safety, since it is possible that one thread is using connect for database operations while another thread is calling closeConnection to close the link.
So for thread-safety reasons, the two methods in this code must be synchronized and synchronized where connect is called.
This can have a significant impact on program performance, because one thread is performing database operations with CONNECT while the other threads are waiting.
So let’s take a closer look at this question. Does this place need to share the CONNECT variable? In fact, no. If each thread has a connect variable, access to the connect variable between threads is virtually independent, meaning that one thread does not need to care whether other threads have made changes to the connect.
At this point, it might occur to you that since you don’t need to share this variable between threads, you can simply create a database connection for specific use in each method that requires a database connection, and then release the connection after the method is called. Like this:
?
12 3 4 5 6 7 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class ConnectionManager { private Connection connect = null ; public Connection openConnection() { if (connect == null ) { connect = DriverManager.getConnection(); } return connect; } public void closeConnection() { if (connect! = null ) connect.close(); } } class Dao{ public void insert() { ConnectionManager connectionManager = new ConnectionManager(); Connection connection = connectionManager.openConnection(); // Use connection to operate connectionManager.closeConnection(); } } |
There’s really nothing wrong with this, because each time the connection is created inside the method, there are naturally no thread-safety issues between threads. But this can have a deadly effect: it can cause server stress and severely affect program performance. Because database connections need to be opened and closed frequently in the method, this can not only severely affect program execution efficiency, but also cause server stress.
ThreadLocal makes perfect sense in this case, because ThreadLocal creates a copy of the variable in each thread. That is, it has an internal copy of the variable in each thread, and can be used anywhere within the thread without affecting each other, so there is no thread safety issue. It does not seriously affect program execution performance.
Note, however, that while ThreadLocal can solve the problems described above, since replicas are created in each thread, consider the resource consumption, such as memory footprint, that would be greater than if ThreadLocal were not used.
Parsing the ThreadLocal class in depth
That said, let’s take a look at how ThreadLocal is implemented.
Let’s take a look at a few methods provided by the ThreadLocal class:
?
1 2 3 4 | public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { } |
The get() method is used to get copies of variables that ThreadLocal keeps in the current thread, set() is used to set copies of variables in the current thread, remove() is used to remove copies of variables in the current thread, and initialValue() is a protected method. Generally used to override when in use, it is a lazy load method, as described below.
First let’s look at how the ThreadLocal class creates a copy of a variable for each thread.
Let’s look at the implementation of the get method:
The first statement is to get the current thread and then get a map of type ThreadLocalMap using the getMap(t) method. And then I’m going to get the <key,value> key-value pair, and notice that I’m going to get the key-value pair this, not the current thread T.
Value is returned on success.
If the map is empty, the setInitialValue method is called to return the value.
Let’s take a closer look at each sentence above:
Let’s first look at what we do in the getMap method:
What you might not expect is that getMap calls the current thread T and returns a member variable threadLocals from the current thread T.
So let’s continue with Thread and see what the member variable threadLocals is:
This type is an inner class of the ThreadLocal class. Let’s look at the implementation of ThreadLocalMap:
As you can see, the Entry of ThreadLocalMap inherits WeakReference and uses ThreadLocal as the key value.
Then move on to the implementation of the setInitialValue method:
If the map is not empty, set the key-value pair to null and create the map.
By now, most of you have probably seen how ThreadLocal creates copies of variables for each thread:
First of all, a ThreadLocal. Inside each Thread Thread ThreadLocalMap types of member variables threadLocals, copy the threadLocals is used to store the actual variable, the key value for the current ThreadLocal variables, Value is a copy of a variable (that is, a variable of type T).
When called from the get() or set() function of a ThreadLocal variable, threadLocals will be initialized with the current function as the key. The copy variable to be saved as value by ThreadLocal is saved to threadLocals.
Then in the current thread, if you want to use duplicate variables, you can look them up in threadLocals using the get method.
Here’s an example of how you can create a copy of a variable in each thread using ThreadLocal:
?
12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class Test { ThreadLocal<Long> longLocal = new ThreadLocal<Long>(); ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); } } |
The output of this code is:
As you can see from the output of this code, longLocal holds a different copy value than stringLocal holds a different copy value in the main and thread1 threads. The last time the replica values are printed on the main thread is to prove that the replica values are indeed different in the main thread and thread1 thread.
To sum up:
1) The actual copies created by ThreadLocal are stored in each thread’s own threadLocals;
2) Why is the key value of type threadLocals for ThreadLocalMap a ThreadLocal object, since there can be multiple ThreadLocal variables per thread, like longLocal and stringLocal in the code above?
3) Set must be set before get, otherwise null pointer exceptions will be reported;
You must override the initialValue() method if you want to access it without calling set before get.
Because in the above code analysis process, we found that if there is no set first, that is, the corresponding store cannot be found in the map, the setInitialValue method will return I, and in the setInitialValue method, One statement is T value = initialValue(), and by default, the initialValue method returns NULL.
Look at this example:
?
12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public class Test { ThreadLocal<Long> longLocal = new ThreadLocal<Long>(); ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); } } |
In the main thread, a null pointer exception will be reported if the main thread does not set but gets directly.
But if you rewrite the initialValue method as follows:
?
12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44, 45, 46, 47, 48 | public class Test { ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){ protected Long initialValue() { return Thread.currentThread().getId(); }; }; ThreadLocal<String> stringLocal = new ThreadLocal<String>(){; protected String initialValue() { return Thread.currentThread().getName(); }; }; public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); } } |
You can call get without setting it first.
3. Application scenarios of ThreadLocal
The most common uses of ThreadLocal are for database connections, Session management, and so on.
Such as:
?
1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); } |
The following code is taken from:
www.iteye.com/topic/10380…
?
12 3 4 5 6 7 8 9 10 11 12 13 14 | private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null ) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; } |
References:
In-depth Understanding of the Java Virtual Machine
Ideas for Java Programming
Ifeve.com/thread-mana…
www.ibm.com/developerwo…
www.iteye.com/topic/10380…
www.iteye.com/topic/77771…
www.iteye.com/topic/75747…
Blog.csdn.net/ghsau/artic…
ispring.iteye.com/blog/162982
Blog.csdn.net/imzoer/arti…
www.blogjava.net/wumi9527/ar…
Bbs.csdn.net/topics/3800…