This article is used by the SimpleDateFormat by concurrent threads unsafe environment optimization case, help understand ThreadLocal.
The company recently finishing the project, found that many relatively bad place to write, such as the following:
public class DateUtil { private final static SimpleDateFormat sdfyhm = new SimpleDateFormat( "yyyyMMdd"); public synchronized static Date parseymdhms(String source) { try { return sdfyhm.parse(source); } catch (ParseException e) { e.printStackTrace(); return new Date(); } } }
function parseymdhms where the () uses a synchronized modification means that the operation is not thread-safe, so need to be synchronized, thread-safe can only be a SimpleDateFormat parse () method, view the source code, in SimpleDateFormat there is a global variable
protected Calendar calendar; a Date the parse () { calendar.clear (); ... // perform some operation, provided calendar date what calendar.getTime (); // Get time calendar }
The clear () operation will cause the thread-safe.
In addition the use of synchronized keyword has a significant impact on performance, especially when multiple threads, each call parseymdhms methods will be synchronized judge, and synchronization overhead itself very large, so it is unreasonable solution.
ways to improve
Thread-safe multi-threaded stems from the use of shared variables cause, so here use ThreadLocal <SimpleDateFormat> to create a separate copy of the variable to each thread, first give the code, and then analyze the causes of this problem.
/ ** * date tools (used ThreadLocal obtain SimpleDateFormat, other methods can be copied directly lang-Common) * @author Niu of Li * @date 2016/11/19 * / public class the DateUtil { Private static the Map <String, ThreadLocal < >> = sdfMap the SimpleDateFormat new new the HashMap <String, the ThreadLocal <>> the SimpleDateFormat (); Private static Logger Logger = LoggerFactory.getLogger (the DateUtil. class ); public Final static String MDHMSS = "MMddHHmmssSSS" ; public Final static String = YMDhms "yyyyMMddHHmmss " ; public Final static String YMDHMS_ = "YYYY-the MM-dd HH: mm: SS" ; public Final static String YMD = "yyyyMMdd" ; public Final static String YMD_ = "YYYY-the MM-dd" ; public Final static String HMS = "HHmmss " ; / ** * the sdf example key in the map to obtain the corresponding thread * @param pattern key in the map * @return this example * / Private static the SimpleDateFormat getSdf ( Final String pattern) { the ThreadLocal <the SimpleDateFormat> = sdfThreadsdfMap.get (pattern); IF (sdfThread == null ) { // double screening, preventing sdfMap was repeatedly put into value, and double lock single embodiment for the same reason the synchronized (the DateUtil. class ) { sdfThread = sdfMap.get (pattern); IF (sdfThread == null ) { logger.debug ( "PUT SDF new new pattern of" pattern + + "to Map" ); sdfThread = new new the ThreadLocal <the SimpleDateFormat> () { @Override protected the SimpleDateFormat the initialValue () { logger.debug ("Thread:" Thread.currentThread + () + "the init pattern:" + pattern); return new new the SimpleDateFormat (pattern); } }; sdfMap.put (pattern, sdfThread); } } } return sdfThread.get (); } / ** * according to a specified pattern resolved date * @param date to be parsed date * @param pattern specified format * @return parsed date instance * / public static a date parseDate (date String, String pattern) { IF (DATE == null ) { the throw new new an IllegalArgumentException ( "Not of The DATE MUST BE null" ); } the try { return getSdf (pattern) .parse (DATE); } the catch (a ParseException E) { e.printStackTrace (); logger.error ( "parsed format is not supported:" + pattern); } return null ; } / ** * format pattern in the specified date * @param dATE dATE to format * @param pattern specified format * @return 解析后格式 */ public static String formatDate(Date date,String pattern){ if (date == null){ throw new IllegalArgumentException("The date must not be null"); }else { return getSdf(pattern).format(date); } } }
test
In performing a main thread, the other two sub-thread execution, the same pattern is used
public static void main(String[] args) { DateUtil.formatDate(new Date(),MDHMSS); new Thread(()->{ DateUtil.formatDate(new Date(),MDHMSS); }).start(); new Thread(()->{ DateUtil.formatDate(new Date(),MDHMSS); }).start(); }
Log Analysis
put new sdf of pattern MMddHHmmssSSS to map thread: Thread[main,5,main] init pattern: MMddHHmmssSSS thread: Thread[Thread-0,5,main] init pattern: MMddHHmmssSSS thread: Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS
analysis
It can be seen sdfMap put into it once, but three times SimpleDateFormat is new, because the code has three threads. So this is why?
For each thread Thread, inside which there is a ThreadLocal.ThreadLocalMap threadLocals
global variable references, ThreadLocal.ThreadLocalMap which has a value corresponding to the ThreadLocal and save a picture is worth a thousand words, the structure is as follows:
So for sdfMap, then changed the structure diagram on the next
? Well log analysis of why this is the case:
1. First, the first executionDateUtil.formatDate(new Date(),MDHMSS);
// first execution DateUtil.formatDate (new Date (), MDHMSS ) Analysis Private static the SimpleDateFormat getSdf ( Final String pattern) { the ThreadLocal <the SimpleDateFormat> sdfThread = sdfMap.get (pattern); // obtained sdfThread is null, into the The if statement if (sdfThread == null ) { the synchronized (DateUtil. class ) { sdfThread = sdfMap.get (pattern); // sdfThread still null, enter the if statement if (sdfThread == null ) { // print log logger.debug("put new sdf of pattern " + pattern + " to map"); //创建ThreadLocal实例,并覆盖initialValue方法 sdfThread = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern); return new SimpleDateFormat(pattern); } }; } }set into such sdfMap// sdfMap.put(pattern,sdfThread); } return sdfThread.get(); }
Some may ask at this time, and there is no call set ThreadLocal method, then the value is set into the how of it?
This requires looking sdfThread.get () implementation:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
That is when the value does not exist will call setInitialValue () method, which calls initialValue () method, which is the way we cover.
The corresponding log printing.
put new sdf of pattern MMddHHmmssSSS to map thread: Thread[main,5,main] init pattern: MMddHHmmssSSS
2. The second sub-thread executionDateUtil.formatDate(new Date(),MDHMSS);
// second child thread executed DateUtil.formatDate `(new new a Date (), MDHMSS);` Private static the SimpleDateFormat getSdf ( Final String pattern) { the ThreadLocal <the SimpleDateFormat> = sdfThread ; sdfMap.get (pattern) // here to give sdfThread is not null, if the block is skipped if (sdfThread == null ) { the synchronized (the DateUtil. class ) { sdfThread = sdfMap.get (pattern); if (sdfThread == null ) { logger.debug ( "PUT new new SDF pattern of "pattern + +" to Map " ); sdfThread = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern); return new SimpleDateFormat(pattern); } }; sdfMap.put(pattern,sdfThread); } } } //直接调用sdfThread.get()返回 return sdfThread.get(); }
Analysis sdfThread.get ()
// second child thread executed DateUtil.formatDate `(new new a Date (), MDHMSS);` public T GET () { the Thread T ; Thread.currentThread = () // get the current sub-thread ThreadLocalMap Map = the getMap (T ); // child thread obtained map is null, if the block is skipped if (map =! null ) { ThreadLocalMap.Entry E = map.getEntry ( the this ); if ! (E = null ) { @SuppressWarnings ( "an unchecked " ) T Result = (T) e.Value; return Result; } } // direct initialization is performed, that is, call initialValue our coverage () method return setInitialValue (); }
The corresponding log:
Thread[Thread-0,5,main] init pattern: MMddHHmmssSSS
Similarly the second and third execution is similar to a direct call sdfThread.get (), then calls the initialValue () method, the corresponding log
Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS
to sum up
在什么场景下比较适合使用ThreadLocal?stackoverflow上有人给出了还不错的回答。
When and how should I use a ThreadLocal variable?
One possible (and common) use is when you have some object that is not thread-safe, but you want to avoid synchronizing access to that object (I’m looking at you, SimpleDateFormat). Instead, give each thread its own instance of the object.
Reference Code:
https://github.com/nl101531/JavaWEB under Util-Demo
Reference:
http://www.importnew.com/21479.html
http://www.cnblogs.com/zemliu/archive/2013/08/29/3290585.html
Author: Bo abandoned _ This update personal blog
link: https: //www.jianshu.com/p/5675690b351e
Source: Jane books
are copyrighted by the author. Commercial reprint please contact the author authorized, non-commercial reprint please indicate the source.