Usage and depth of ThreadLocal class in Java multi-thread programming, javathreadlocal usage
ThreadLocal, literally translated as "Thread Local" or "local thread". If you really think so, it will be wrong! In fact, it is a container used to store local variables of the thread. I think it should be called ThreadLocalVariable (local variable of the thread). I really don't understand why Sun engineers named it like this.
As early as the JDK 1.2 era, java. lang. ThreadLocal was born. It was designed to solve the problem of multi-thread concurrency, but it was designed to be somewhat difficult to use, so it has not been widely used. In fact, it is quite useful. If you don't believe it, let's take a look at this example.
The program of a serial number generator may have multiple threads concurrently accessing it at the same time. Ensure that the serial number obtained by each thread is auto-incrementing without mutual interference.
First define an interface:
public interface Sequence { int getNumber();}
Each time you call the getNumber () method, you can obtain a serial number. When you call it again, the serial number is automatically increased.
Make another thread class:
public class ClientThread extends Thread { private Sequence sequence; public ClientThread(Sequence sequence) { this.sequence = sequence; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber()); } }}
The serial number corresponding to the thread name three times in a row.
We don't need to use ThreadLocal to create an implementation class.
public class SequenceA implements Sequence { private static int number = 0; public int getNumber() { number = number + 1; return number; } public static void main(String[] args) { Sequence sequence = new SequenceA(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); }}
The initial value of the serial number is 0. Three threads are simulated in the main () method. The result is as follows:
Thread-0 => 1Thread-0 => 2Thread-0 => 3Thread-2 => 4Thread-2 => 5Thread-2 => 6Thread-1 => 7Thread-1 => 8Thread-1 => 9
The thread startup sequence is random, so it is not in the order of 0, 1, and 2. Why does Thread-2 output 4, 5, and 6 after Thread-0 outputs 1, 2, and 3? Static variables are shared between threads! This is the so-called "non-thread security" problem.
So how to ensure "thread security? In this case, different threads can have their own static variables. How can this problem be solved? Let's take a look at another implementation.
public class SequenceB implements Sequence { private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceB(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); }}
ThreadLocal encapsulates an Integer-type numberContainer static member variable with an initial value of 0. Let's look at the getNumber () method. First, get the current value from numberContainer, add 1, then set it to numberContainer, and finally get the current value from numberContainer and return it.
Is it disgusting? But very powerful! It is indeed a little time-consuming. We may consider ThreadLocal as a container, which is easy to understand. Therefore, the word Container is used as the suffix to name the ThreadLocal variable.
How is the running result? Check it out.
Thread-0 => 1Thread-0 => 2Thread-0 => 3Thread-2 => 1Thread-2 => 2Thread-2 => 3Thread-1 => 1Thread-1 => 2Thread-1 => 3
Each thread is independent of each other. It is also a static variable. For different threads, It is not shared, but each thread has a copy, which ensures thread security. That is to say, TheadLocal provides an independent copy for each thread!
After figuring out the principle of ThreadLocal, it is necessary to summarize the ThreadLocal API, which is actually very simple.
- Public void set (T value): place the value in the local variable of the thread.
- Public T get (): get the value from the local variable of the thread
- Public void remove (): remove the value from the local variable of the thread (this helps JVM garbage collection)
- Protected T initialValue (): returns the initial value in the local variable of the thread (default: null)
Why is the initialValue () method protected? It is to remind programmers that this method is implemented by you. Please give this thread a local variable an initial value.
After learning about the principles and these APIs, do you actually think that ThreadLocal does not encapsulate a Map? You can write a ThreadLocal file by yourself. Try it.
public class MyThreadLocal<T> { private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>()); public void set(T value) { container.put(Thread.currentThread(), value); } public T get() { Thread thread = Thread.currentThread(); T value = container.get(thread); if (value == null && !container.containsKey(thread)) { value = initialValue(); container.put(thread, value); } return value; } public void remove() { container.remove(Thread.currentThread()); } protected T initialValue() { return null; }}
The above completely copies a ThreadLocal, which defines a synchronous Map (why? The Code should be easy to understand.
Next we will use this MyThreadLocal to implement it.
public class SequenceC implements Sequence { private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceC(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); }}
The above code is actually replacing ThreadLocal with MyThreadLocal. That's all. The running effect is the same as before, and it is also correct.
In fact, ThreadLocal can be a separate design mode, depending on what you think.
What are the specific use cases of ThreadLocal?
The first thing I want to talk about is: store JDBC Connection through ThreadLocal to achieve transaction control.
Keep my consistent Style. Use a Demo. The user puts forward a requirement: when the product price is modified, the operation logs must be recorded and the operation logs must be recorded.
Presumably, this case should have been met as long as we were friends of application systems? There are two tables in the database: product and log. Two SQL statements can be used to solve the problem:
update product set price = ? where id = ?insert into log (created, description) values (?, ?)
But! Make sure that the two SQL statements must be committed in the same transaction; otherwise, the update statement may have been committed, but the insert statement has not been committed. If this happens, we will be pointed to and scolded by users: "Why can't we see the product price change ?".
After receiving this demand, I am smart enough to do this:
First, I wrote a DBUtil tool class that encapsulates common database operations:
Public class DBUtil {// configure private static final String driver = "com. mysql. jdbc. driver "; private static final String url =" jdbc: mysql: // localhost: 3306/demo "; private static final String username =" root "; private static final String password = "root"; // defines a database Connection private static Connection conn = null; // gets the Connection public static Connection getConnection () {try {Class. forName (driver); conn = Drive RManager. getConnection (url, username, password);} catch (Exception e) {e. printStackTrace () ;}return conn ;}// close the public static void closeConnection () {try {if (conn! = Null) {conn. close () ;}} catch (Exception e) {e. printStackTrace ();}}}
There is a static Connection in it, so the sub-database Connection is easy to operate!
Then, I defined an interface for calling the logic layer:
public interface ProductService { void updateProductPrice(long productId, int price);}
Based on your requirements, I think this interface is sufficient. Update the price of the corresponding Product based on productId, and insert a data record to the log table.
In fact, the business logic is not complex, so I quickly completed the implementation class of the ProductService interface:
Public class ProductServiceImpl implements ProductService {private static final String UPDATE_PRODUCT_ SQL = "update product set price =? Where id =? "; Private static final String INSERT_LOG_ SQL =" insert into log (created, description) values (?, ?) "; Public void updateProductPrice (long productId, int price) {try {// get Connection conn = DBUtil. getConnection (); conn. setAutoCommit (false); // disable automatic transaction commit (enable transaction) // execute updateProduct (conn, UPDATE_PRODUCT_ SQL, productId, price); // update product insertLog (conn, INSERT_LOG_ SQL, "Create product. "); // Insert the log // submit the transaction conn. commit ();} catch (Exception e) {e. printStackTrace ();} finally {// close the connection to DBUtil. closeCo Nnection () ;}} private void updateProduct (Connection conn, String updateProductSQL, long productId, int productPrice) throws Exception {PreparedStatement pstmt = conn. prepareStatement (updateProductSQL); pstmt. setInt (1, productPrice); pstmt. setLong (2, productId); int rows = pstmt.exe cuteUpdate (); if (rows! = 0) {System. out. println ("Update product success! ") ;}} Private void insertLog (Connection conn, String insertLogSQL, String logDescription) throws Exception {PreparedStatement pstmt = conn. prepareStatement (insertLogSQL); pstmt. setString (1, new SimpleDateFormat ("yyyy-MM-dd HH: mm: ss SSS "). format (new Date (); pstmt. setString (2, logDescription); int rows = pstmt.exe cuteUpdate (); if (rows! = 0) {System. out. println ("Insert log success! ");}}}
Is the Code quite readable? Here I have used the advanced feature Transaction of JDBC. Fortunately, I thought it was necessary to write a client to test the execution result? So I am lazy and added a main () method directly in ProductServiceImpl:
public static void main(String[] args) { ProductService productService = new ProductServiceImpl(); productService.updateProductPrice(1, 3000);}
I want to change the product price of productId to 1 to 3000. So I ran the program again, and the console output:
Update product success!Insert log success!
Yes. As a professional programmer, I have to go to the database to check it out. That's right! The records corresponding to the product table are updated, and a record is inserted in the log table. In this way, you can deliver the ProductService interface to others for calling.
A few hours later, my QA sister began to scold me: "I rely on it! I have simulated 10 requests. Why is this interface suspended? The database connection is closed !".
When I heard such a call, I trembled and immediately interrupted my small video. I quickly opened the IDE and found the ProductServiceImpl implementation class. Does it seem like there are no bugs? But I am afraid to give her any response, and I am afraid of her.
I suddenly remembered that she used a tool to simulate multiple threads! I can simulate it myself, so I wrote a Thread class:
public class ClientThread extends Thread { private ProductService productService; public ClientThread(ProductService productService) { this.productService = productService; } @Override public void run() { System.out.println(Thread.currentThread().getName()); productService.updateProductPrice(1, 3000); }}
I use this thread to call the ProduceService method to see if there is any problem. Now, I need to modify the main () method again:
// public static void main(String[] args) {// ProductService productService = new ProductServiceImpl();// productService.updateProductPrice(1, 3000);// } public static void main(String[] args) { for (int i = 0; i < 10; i++) { ProductService productService = new ProductServiceImpl(); ClientThread thread = new ClientThread(productService); thread.start(); }}
I want to simulate 10 threads too. I don't believe that!
The running results really make me dizzy:
Thread-1Thread-3Thread-5Thread-7Thread-9Thread-0Thread-2Thread-4Thread-6Thread-8Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)at java.lang.reflect.Constructor.newInstance(Constructor.java:513)at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)at com.mysql.jdbc.Util.getInstance(Util.java:386)at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1304)at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1296)at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1699)at com.smart.sample.test.transaction.solution1.ProductServiceImpl.updateProductPrice(ProductServiceImpl.java:25)at com.smart.sample.test.transaction.ClientThread.run(ClientThread.java:18)
Rely on me! An error was reported in a multi-threaded environment, and the database connection was closed. What's going on? I am in meditation. So I copied the error message and found it in Baidu, Google, and OSC. The answer is really strange.
I suddenly remembered that since it is related to Connection, I will focus on checking Connection-related code. Shouldn't Connection be static? I originally designed static to make it easier to access DBUtil's static method. Using static variables to store connections also improves the performance. How can this problem be solved?
So I saw a very popular article "ThreadLocal" on OSC, and finally let me understand it! It turns out that every thread has its own connection instead of sharing the same connection. Otherwise thread 1 may close the connection of thread 2, so thread 2 will report an error. This must be the case!
I quickly reconstructed DBUtil:
Public class DBUtil {// configure private static final String driver = "com. mysql. jdbc. driver "; private static final String url =" jdbc: mysql: // localhost: 3306/demo "; private static final String username =" root "; private static final String password = "root"; // defines a local thread variable for storing database connections (so that each thread has its own connection) private static ThreadLocal <Connection> connContainer = new ThreadLocal <Connection> (); // gets the public stat Ic Connection getConnection () {Connection conn = connContainer. get (); try {if (conn = null) {Class. forName (driver); conn = DriverManager. getConnection (url, username, password) ;}} catch (Exception e) {e. printStackTrace ();} finally {connContainer. set (conn) ;}return conn ;}// close the public static void closeConnection () {Connection conn = connContainer. get (); try {if (conn! = Null) {conn. close () ;}} catch (Exception e) {e. printStackTrace () ;}finally {connContainer. remove ();}}}
I put the Connection in ThreadLocal, so that every thread is isolated and will not interfere with each other.
In addition, in the getConnection () method, first obtain the Connection from ThreadLocal (that is, in connContainer). If not, create a Connection through JDBC, finally, put the created connection in this ThreadLocal. ThreadLocal can be regarded as a container, which is not false at all.
Similarly, I have reconstructed the closeConnection () method. First, obtain the Connection from the container, close it, and remove it from the container to keep the container clean.
Should this be done? Run the main () method again:
Thread-0Thread-2Thread-4Thread-6Thread-8Thread-1Thread-3Thread-5Thread-7Thread-9Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!Update product success!Insert log success!
Finally solved the problem.