This article helps everyone understand ThreadLocal by using SimpleDateFormat optimization cases that are unsafe in concurrent environments.
Recently, I sorted out the company's projects and found that there are many bad things written, 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(); } }} First, let’s analyze:
The function parseymdhms() at this point uses synchronized modification, which means that the operation is thread-unsafe, so it needs to be synchronized. The thread-unsafe can only be the parse() method of SimpleDateFormat. Check the source code. There is a global variable in SimpleDateFormat.
protected Calendar calendar;Date parse() { calendar.clear(); ... // Perform some operations to set the date of calendar and other calendar.getTime(); // Get the time of calendar}The clear() operation will cause thread insecure.
In addition, using the synchronized keyword has a great impact on performance, especially when multi-threading, every time the parseymdhms method is called, the synchronization judgment will be made, and the synchronization itself is very expensive, so this is an unreasonable solution.
Improvement method
Thread insecure is caused by the use of shared variables by multiple threads, so here we use ThreadLocal<SimpleDateFormat> to create a copy variable for each thread separately. First give the code, and then analyze the cause of this problem to solve the problem.
/** * Date tool class (ThreadLocal is used to get SimpleDateFormat, and other methods can be copied directly by common-lang) * @author Niu Li * @date 2016/11/19 */public class DateUtil { private static Map<String,ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>(); private static Logger logger = LoggerFactory.getLogger(DateUtil.class); public final static String MDHMSS = "MMddHHmmssSSS"; public final static String YMDHMS = "yyyMMddHHmmss"; public final static String YMDHMS_ = "yyyy-MM-dd HH:mm:ss"; public final static String YMD = "yyyyMMdd"; public final static String YMD_ = "yyyy-MM-dd"; public final static String HMS = "HHmmss"; /** * Get the sdf instance of the corresponding thread based on the key in the map* @param pattern key in the map * @return This instance*/ private static SimpleDateFormat getSdf(final String pattern){ ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern); if (sdfThread == null){ //Double check to prevent sdfMap from putting the value into multiple times, and the reason for the double lock singleton is the same synchronized (DateUtil.class){ sdfThread = sdfMap.get(pattern); if (sdfThread == null){ logger.debug("put new sdf of pattern " + 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); } } } return sdfThread.get(); } /** * Parsing the date according to the specified pattern* @param date The date to be parsed * @param date pattern Specify format * @return Parsed date instance*/ public static Date parseDate(String date,String pattern){ if(date == null) { throw new IllegalArgumentException("The date must not be null"); } try { return getSdf(pattern).parse(date); } catch (ParseException e) { e.printStackTrace(); logger.error("The parsed format does not support:"+pattern); } return null; } /** * Format date according to the specified pattern* @param date The date to be formatted * @param pattern Specify the format* @return Parsed format*/ 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
Execute one in the main thread and the other two in the child thread, both using the same pattern
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 mapthread: Thread[main,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-0,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-1,5,main] init pattern: MMddHHmmssSSSS
analyze
It can be seen that sdfMap put entered once, while SimpleDateFormat was new three times because there were three threads in the code. So why is this?
For each thread Thread, there is a global variable reference to ThreadLocal.ThreadLocalMap threadLocals. There is a ThreadLocal.ThreadLocalMap that holds the ThreadLocal and the corresponding value. One picture is better than a thousand words. The structure diagram is as follows:
So for sdfMap, the structure diagram will be changed
1. First execute DateUtil.formatDate(new Date(), MDHMSS);
//The first time I executed DateUtil.formatDate(new Date(), MDHMSS) analysis private static SimpleDateFormat getSdf(final String pattern){ ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern); //The obtained sdfThread is null, enter if statement if (sdfThread == null){ synchronized (DateUtil.class){ sdfThread = sdfMap.get(pattern); //sdfThread is still null, enter if statement if (sdfThread == null){ //Print logger.debug("put new sdf of pattern " + pattern + " to map"); //Create ThreadLocal instance and override initialValue method sdfThread = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern); return new SimpleDateFormat(pattern); } }; //Set into sdfMap sdfMap.put(pattern,sdfThread); } } } return sdfThread.get(); } At this time, someone may ask, the ThreadLocal set method is not called here, so how do you set the value to enter?
This requires the implementation of sdfThread.get():
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 to say, when the value does not exist, the setInitialValue() method will be called, which will call the initialValue() method, which is the method we override.
Corresponding log printing.
put new sdf of pattern MMddHHmmssSSS to mapthread: Thread[main,5,main] init pattern: MMddHHmmssSSS
2. Execute DateUtil.formatDate(new Date(), MDHMSS) on the child thread the second time;
//Execute `DateUtil.formatDate(new Date(),MDHMSS);` private static SimpleDateFormat getSdf(final String pattern){ ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern); //The sdfThread obtained here is not null, skip if block if (sdfThread == null){ synchronized (DateUtil.class){ sdfThread = sdfMap.get(pattern); if (sdfThread == null){ logger.debug("put new sdf of pattern " + 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); } } } //Call sdfThread.get() directly to return sdfThread.get(); }Analysis sdfThread.get()
//Execute `DateUtil.formatDate(new Date(),MDHMSS);` public T get() { Thread t = Thread.currentThread();//Get the current child thread ThreadLocalMap map = getMap(t); //The map obtained in the child thread is null, skip if block if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //Execute the initialization directly, that is, call the initialValue() method we overwrite return setInitialValue(); }Corresponding log:
Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS
Summarize
In which scenario is more suitable for using ThreadLocal? Someone gave a pretty good answer on 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/Util-Demo under JavaWEB
References:
Learn Java ThreadLocal in an easy-to-understand way
SimpleDateFormat's thread safety problem and solution
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.