ThreadLocal, literal translation is "thread local" or "local thread", if you really think so, it is wrong! In fact, it is a container for storing the thread's local variables, I think it should be called threadlocalvariable (thread local variable) is right, I really do not understand why the original Sun company engineers named.
Early in the JDK 1.2 era, Java.lang.ThreadLocal was born, it is designed to solve the problem of multithreading concurrency, but designed to be somewhat difficult to use, so far has not been widely used. In fact, it is very useful, do not believe it, let's take a look at this example.
A Sequence number generator program, you may have multiple threads concurrently access it, to ensure that each thread gets the serial number is self-increment, and can not interfere with each other.
Define an interface first:
Public interface Sequence { int getnumber ();}
Each call to the GetNumber () method gets a sequence number that is automatically added the next time it is called.
Then do a 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 (). Getna Me () + "= +" + sequence.getnumber ());}}
Sequential output of three thread names and their corresponding serial numbers in-line.
We do not need to ThreadLocal, to make an implementation of the class bar.
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 sequence number is 0, which simulates three threads in the main () method, with the following results:
Thread-0 = 1thread-0 = 2thread-0 = 3thread-2 = 4thread-2 = 5thread-2 = 6thread-1 = 7thread-1 = > 8thread-1 = 9
Since the thread boot order is random, it is not the order of 0, 1, 2, which is a good understanding. Why did the Thread-0 output 1, 2, 3, and Thread-2 output 4, 5, 6? The static variables are shared between threads! This is the so-called "non-thread-safe" issue.
So how do you ensure "thread safety"? Corresponds to this case, that is, different threads can have their own static variables, how to implement it? Let's take a look at another implementation.
public class Sequenceb implements Sequence { private static ThreadLocal
numbercontainer = new ThreadLocal
() { @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 (); }}
An Integer type of Numbercontainer static member variable is encapsulated by ThreadLocal, and the initial value is 0. Then look at the GetNumber () method, first get out of the current value from the Numbercontainer, add 1, then set to Numbercontainer, and finally the Numbercontainer in the get out of the current value and return.
Isn't it disgusting? But very powerful! Indeed, a little bit of a break, we might as well think of ThreadLocal as a container, so the understanding is simple. Therefore, the word Container is deliberately used as a suffix to name the ThreadLocal variable.
What is the result of the operation? Look at it.
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 and is also a static variable, and for different threads it is not shared, but each thread is a copy, which also guarantees thread safety. In other words, Theadlocal provides a separate copy for each thread!
After figuring out the principle of ThreadLocal, it is necessary to summarize the ThreadLocal API, which is very simple indeed.
- public void Set (T value): Puts the value in the thread local variable
- Public T get (): Gets the value from the thread local variable
- public void Remove (): Remove value from thread local variable (helps JVM garbage collection)
- Protected T InitialValue (): Returns the initial value in the thread local variable (default is null)
Why is the InitialValue () method protected? Just to remind programmers that this approach is for you to implement, give this thread local variables an initial value.
Understand the principle and these APIs, in fact, think ThreadLocal inside is encapsulated a Map? You can write a ThreadLocal, try it.
public class Mythreadlocal
{ private Map
container = Collections.synchronizedmap (new HashMap
()); 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; }}
Above completely cottage a ThreadLocal, in which a sync Map is defined (why should this be?). Ask the reader to think for themselves), the code should be very easy to read.
Below with this mythreadlocal again to realize a look.
public class SEQUENCEC implements Sequence { private static mythreadlocal
numbercontainer = new Mythreadlocal
() { @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 to replace the ThreadLocal with the mythreadlocal, only that, the operation effect and the same as before, is also correct.
In fact, ThreadLocal can become a design mode alone, it depends on how you look.
What are the specific use cases of ThreadLocal?
The first thing I want to say is to store the JDBC Connection through ThreadLocal to achieve the ability to control the transaction.
Still keep my Style, use a Demo to speak. The user proposes a requirement: when the price of the product is modified, it is necessary to record the operation log and when to do something.
Presumably this case, as long as the application system of the small partners, should have encountered it? There are two tables in the database: Product and log, with two SQL statements that should solve the problem:
Update product Set price =? WHERE id =? INSERT into log (created, description) VALUES (?,?)
but! To make sure that the two SQL statements must be committed in the same transaction, it is possible that the update was committed, but the insert was not committed. If such a thing really happened, we will be the user pointed to the nose crazy scold: "Why the product price changed, but do not see when to change it?" ”。
Smart me after receiving this demand, is to do this:
First, I write a Dbutil tool class that encapsulates the common operations of the database:
public class Dbutil { //database config 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"; Define a database connection private static Connection conn = null; Gets the connection public static Connection getconnection () { try { class.forname (driver); conn = drivermanager.getconnection (URL, username, password); } catch (Exception e) { e.printstacktrace (); } return conn; } Close connection public static void CloseConnection () { try { if (conn! = null) { conn.close () } } catch (Exception e) { e.printstacktrace ();}} }
Inside made a static Connection, this database connection is good operation, awesome bar!
I then defined an interface for the logical layer to invoke:
Public interface Productservice { void Updateproductprice (long productId, int price);}
According to the user's request, I think this interface is fully sufficient. Update the price of the corresponding Product according to ProductId, and then insert a piece of data into the log table.
In fact, the business logic is not too complex, so I quickly completed the implementation of the Productservice interface class:
public class Productserviceimpl implements Productservice {private static final String Update_product_sql = "UPDATE prod UCT 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 Connection conn = Dbutil.getconnect Ion (); Conn.setautocommit (FALSE); Turn off Autocommit transactions (open transactions)//Perform operations UPDATEPRODUCT (conn, Update_product_sql, ProductId, Price); New Product Insertlog (conn, insert_log_sql, "Create product."); Insert Log//COMMIT transaction conn.commit (); } catch (Exception e) {e.printstacktrace (); } finally {//close connection dbutil.closeconnection (); }} private void UpdateProduct (Connection conn, String Updateproductsql, long productId, int productprice) throws Except Ion {PreparedStatement pstmt = conn.preparestatement (Updateproductsql); Pstmt.setint (1, productprice); Pstmt.setlong (2, productId); int rows = Pstmt.executeupdate (); if (Rows! = 0) {System.out.println ("Update product success!"); }} private void Insertlog (Connection conn, string insertlogsql, String logdescription) throws Exception {PREPAREDST Atement 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.executeupdate (); if (Rows! = 0) {System.out.println ("Insert log success!"); } }}
Is the code very readable? Here I have used the advanced features of JDBC Transaction. After a while, I think it is necessary to write a client, to test the results of the implementation is not what I want it? So I was lazy and added a main () method directly to the Productserviceimpl:
public static void Main (string[] args) { Productservice productservice = new Productserviceimpl (); Productservice.updateproductprice (1, 3000);}
I want to change the price of the ProductId 1 product to 3000. So I ran the program over the console output:
Update Product success! Insert Log success!
It should be right. As a professional programmer, in order to be foolproof, I must go to the database to see. That's right! The records for the product table are updated, and a record is inserted into the log table. This allows the Productservice interface to be delivered to someone else to invoke.
A few hours later, QA sister began to scold me: "I am!" I just simulated 10 requests, why did you hang up on this interface? It says the database connection is off! ”。
Hear such a cry, let me tremble, immediately interrupted my little video, quickly open the IDE, found this Productserviceimpl this implementation class. Like there's no Bug? But now I dare not give her any response, I am indeed a little afraid of her.
It occurred to me that she was simulating multiple threads with tools. I can also simulate ah, 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, +);} }
I use this thread to call the Produceservice method to see if there is a problem. At this point, I'm going to change the main () method again:
public static void Main (string[] args) {// productservice productservice = new Productserviceimpl ();/ / Productservice.updateproductprice (1, +),//} 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 also simulate 10 threads, I do not believe that evil!
The running results really made me dizzy and 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)
Holy shit! Unexpectedly in the multi-threaded environment error, and sure enough, the database connection is closed. What's going on here? I was in a deep meditation. So I copied a Copy of the error message, in Baidu, Google, and OSC are looking for, answer is really strange.
It occurred to me that since I had a relationship with Connection, I would focus on checking Connection related code. Is Connection not supposed to be static? I originally designed to be static mainly to make Dbutil static method access more convenient, with static variables to store Connection also improve performance AH. What are you doing?
So I saw a very hot on the OSC of an article "ThreadLocal that Little Thing", finally let me understand! To make each thread have its own connection, instead of sharing the same connection, thread 1 may turn off thread 2 connections, so thread 2 is an error. That must be it!
I hastened to reconstruct the dbutil:
public class Dbutil {//database config 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 placing a database connection (so that each thread has its own connection) private static ThreadLocal
Conncontainer = new ThreadLocal
(); Get connection public static 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 connection 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 the ThreadLocal so that each thread is isolated and does not interfere with each other.
In addition, in the Getconnection () method, first obtain the Connection from the ThreadLocal (that is, conncontainer), if not, create the connection through JDBC, and finally put the created connection into this Threadlo Cal. ThreadLocal can be seen as a container, not fake.
Likewise, I have refactored the CloseConnection () method, first getting the Connection from the container, closing it off, and finally remove it from the container to keep the container clean.
It's supposed to be okay, right? I 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!
It's finally settled.