Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

❤️ author introduction: Java field quality creator 🏆, CSDN blog expert certification 🏆, Huawei cloud enjoy expert certification 🏆 🏆 technology work, the reward is to praise the user collection again look, form a habit

scenario

Before java8, to format dates and times, you needed SimpleDateFormat.

But we know that SimpleDateFormat is not thread-safe, so you have to handle it with care, lock it or not define it as static, new the object inside the method, and then format it. It is very troublesome, and repeatedly new objects, also increased memory overhead.

Why are SimpleDateFormat threads thread unsafe?

SimpleDateFormat: SimpleDateFormat

// Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
        // Convert input date to time field listcalendar.setTime(date); . }Copy the code

The problem is with the member variable Calendar. If you define it static when using SimpleDateFormat, then SimpleDateFormat becomes a shared variable. The Calendar in SimpleDateFormat can then be accessed by multiple threads.

SimpleDateFormat’s parse method is also thread-unsafe:

 public Date parse(String text, ParsePosition pos)
    {... Date parsedDate;try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime(); }}}// An IllegalArgumentException will be thrown by Calendar.getTime()
        // if any fields are out of range, e.g., MONTH == 17.
        catch (IllegalArgumentException e) {
            pos.errorIndex = start;
            pos.index = oldStart;
            return null;
        }

        return parsedDate;  
 }
Copy the code

**parsedDate = calb.establish(calendar).getTime(); ** Gets the return value. The parameter of the calendar method is Calendar. Calendar can be accessed by multiple threads, causing thread insecurity.

Calb. Establish (Calendar)**

The calb.establish(Calendar) method calls cal.clear() and cal.set() first, and then sets the values. However, these two operations are not atomic, and there is no thread-safe mechanism to ensure that the value of CAL may cause problems in the case of multithreading concurrency.

Verify that SimpleDateFormat threads are not safe

public class SimpleDateFormatDemoTest {

	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
    		Create a thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // assign tasks to the thread pool
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
            pool.submit(threadPoolTest);
        }
        // close the thread pool
        pool.shutdown();
    }


    static class  ThreadPoolTest implements Runnable{

        @Override
        public void run(a) {
				String dateString = simpleDateFormat.format(new Date());
				try {
					Date parseDate = simpleDateFormat.parse(dateString);
					String dateString2 = simpleDateFormat.format(parseDate);
					System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
				} catch (Exception e) {
					System.out.println(Thread.currentThread().getName()+"Formatting failed"); }}}}Copy the code

False occurs twice, indicating that the thread is unsafe. But also throw exceptions, this is serious.

The solution

Solution 1: Don’t define static variables, use local variables

To format or parse using SimpleDateFormat objects, define them as local variables. It’s thread safe.

public class SimpleDateFormatDemoTest1 {

    public static void main(String[] args) {
    		Create a thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // assign tasks to the thread pool
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
            pool.submit(threadPoolTest);
        }
        // close the thread pool
        pool.shutdown();
    }


    static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			String dateString = simpleDateFormat.format(new Date());
			try {
				Date parseDate = simpleDateFormat.parse(dateString);
				String dateString2 = simpleDateFormat.format(parseDate);
				System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()+"Formatting failed"); }}}}Copy the code

As you can see from the figure, thread-safety is ensured, but this scheme is not recommended in high-concurrency scenarios because a large number of SimpleDateFormat objects will be created, affecting performance.

Solution 2: Synchronized Lock and Lock Lock

Add synchronized lock

The SimpleDateFormat object is defined as a global variable, and synchronized is used to ensure thread-safety when SimpleDateFormat needs to be called to format the time.

public class SimpleDateFormatDemoTest2 {

	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
    		Create a thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // assign tasks to the thread pool
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
            pool.submit(threadPoolTest);
        }
        // close the thread pool
        pool.shutdown();
    }

    static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			try {
				synchronized (simpleDateFormat){
					String dateString = simpleDateFormat.format(new Date());
					Date parseDate = simpleDateFormat.parse(dateString);
					String dateString2 = simpleDateFormat.format(parseDate);
					System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2)); }}catch (Exception e) {
				System.out.println(Thread.currentThread().getName()+"Formatting failed"); }}}}Copy the code

As shown, threads are safe. The global variable SimpleDateFormat is defined, reducing the cost of creating a large number of SimpleDateFormat objects. However, with synchronized locks, only one thread can execute the locked code block at a time, which can affect performance in high concurrency situations.However, this scheme is not recommended for high concurrency scenarios

Add the Lock Lock

The principle of Lock Lock is the same as that of synchronized Lock. Lock mechanism is used to ensure thread security.

public class SimpleDateFormatDemoTest3 {

	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
    		Create a thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // assign tasks to the thread pool
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
            pool.submit(threadPoolTest);
        }
        // close the thread pool
        pool.shutdown();
    }

    static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			try {
				lock.lock();
					String dateString = simpleDateFormat.format(new Date());
					Date parseDate = simpleDateFormat.parse(dateString);
					String dateString2 = simpleDateFormat.format(parseDate);
					System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()+"Formatting failed");
			}finally{ lock.unlock(); }}}}Copy the code

Lock can also be used to ensure thread safety. Note that the lock must be released at the end of the code inFinally added lock.unlock();To ensure lock release. Performance is affected in the case of high concurrency.This scheme is not recommended in high concurrency scenarios

Solution 3: Use ThreadLocal

Use ThreadLocal to ensure that each thread has a copy of the SimpleDateFormat object. This keeps the thread safe.

public class SimpleDateFormatDemoTest4 {

	private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
		@Override
		protected DateFormat initialValue(a) {
			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }};public static void main(String[] args) {
    		Create a thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // assign tasks to the thread pool
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
            pool.submit(threadPoolTest);
        }
        // close the thread pool
        pool.shutdown();
    }

    static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			try {
					String dateString = threadLocal.get().format(new Date());
					Date parseDate = threadLocal.get().parse(dateString);
					String dateString2 = threadLocal.get().format(parseDate);
					System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()+"Formatting failed");
			}finally {
				// To avoid memory leaks, call the remove method after using threadLocal to clean up datathreadLocal.remove(); }}}}Copy the code

Using ThreadLocal is thread-safe and efficient. Suitable for high concurrency scenarios.

Solution 4: Use DateTimeFormatter instead of SimpleDateFormat

Using DateTimeFormatter instead of SimpleDateFormat (DateTimeFormatter is thread-safe, Java 8+ supports) DateTimeFormatter introduces portal: Swastika blog teaches you to understand Java source code date and time related usage

public class DateTimeFormatterDemoTest5 {
	private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

	public static void main(String[] args) {
		Create a thread pool
		ExecutorService pool = Executors.newFixedThreadPool(5);
		// assign tasks to the thread pool
		ThreadPoolTest threadPoolTest = new ThreadPoolTest();
		for (int i = 0; i < 10; i++) {
			pool.submit(threadPoolTest);
		}
		// close the thread pool
		pool.shutdown();
	}


	static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			try {
				String dateString = dateTimeFormatter.format(LocalDateTime.now());
				TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);
				String dateString2 = dateTimeFormatter.format(temporalAccessor);
				System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println(Thread.currentThread().getName()+"Formatting failed"); }}}}Copy the code

Using DateTimeFormatter is thread safe and efficient.Suitable for high concurrency scenarios.

Solution 5: Replace SimpleDateFormat with FastDateFormat

Replace SimpleDateFormat with FastDateFormat (FastDateFormat is thread-safe and supported by the Apache Commons Lang package, regardless of the Java version)

public class FastDateFormatDemo6 {
	private static FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

	public static void main(String[] args) {
		Create a thread pool
		ExecutorService pool = Executors.newFixedThreadPool(5);
		// assign tasks to the thread pool
		ThreadPoolTest threadPoolTest = new ThreadPoolTest();
		for (int i = 0; i < 10; i++) {
			pool.submit(threadPoolTest);
		}
		// close the thread pool
		pool.shutdown();
	}


	static class  ThreadPoolTest implements Runnable{

		@Override
		public void run(a) {
			try {
				String dateString = fastDateFormat.format(new Date());
				Date parseDate =  fastDateFormat.parse(dateString);
				String dateString2 = fastDateFormat.format(parseDate);
				System.out.println(Thread.currentThread().getName()+"Thread safety:"+dateString.equals(dateString2));
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println(Thread.currentThread().getName()+"Formatting failed"); }}}}Copy the code

Using FastDateFormat is thread safe and efficient. Suitable for high concurrency scenarios.

FastDateFormat source code analysis

 Apache Commons Lang 3.5
Copy the code
//FastDateFormat
@Override
public String format(final Date date) {
   return printer.format(date);
}

	@Override
	public String format(final Date date) {
		final Calendar c = Calendar.getInstance(timeZone, locale);
		c.setTime(date);
		return applyRulesToString(c);
	}
Copy the code

Source code Calender is created in the format method, certainly will not appear setTime thread safety issues. This solves the thread safety puzzle. There are performance issues to consider, right?

So how do we get FastDateFormat

FastDateFormat.getInstance();
FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);
Copy the code

Take a look at the corresponding source

/** * Get the FastDateFormat instance, using the default format and locale **@return FastDateFormat
 */
public static FastDateFormat getInstance(a) {
   return CACHE.getInstance();
}

/** * Get the FastDateFormat instance, using the default locale <br> * to support caching **@paramThe pattern use {@linkJava. Text. * SimpleDateFormat} the same date format@return FastDateFormat
 * @throwsIllegalArgumentException Date format issue */
public static FastDateFormat getInstance(final String pattern) {
   return CACHE.getInstance(pattern, null.null);
}
Copy the code

So here we have a CACHE, so it looks like we’re using a CACHE, so let’s go down

private static final FormatCache<FastDateFormat> CACHE = new FormatCache<FastDateFormat>(){
   @Override
   protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
      return newFastDateFormat(pattern, timeZone, locale); }};//
abstract class FormatCache<F extends Format> {...private final ConcurrentMap<Tuple, F> cInstanceCache = new ConcurrentHashMap<>(7);

	private static final ConcurrentMap<Tuple, String> C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7); . }Copy the code

ConcurrentMap is added to the getInstance method to improve performance. And we know that ConcurrentMap is also thread-safe.

practice

/** * date format {@linkFastDateFormat} : yyyy - MM * /
public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);
Copy the code

//FastDateFormat
public static FastDateFormat getInstance(final String pattern) {
   return CACHE.getInstance(pattern, null.null);
}
Copy the code

As can be seen in the figure, ConcurrentMap is used for caching. And the key is the format, and the time zone and locale are the same key.

conclusion

This is the stipulation in alibaba Java development manual:

1. Don’t define static variables, use local variables

2, synchronized Lock: synchronized Lock

3. Use ThreadLocal

4. Use DateTimeFormatter instead of SimpleDateFormat (DateTimeFormatter is thread-safe, Java 8+ support)

5. Replace SimpleDateFormat with FastDateFormat (FastDateFormat is thread-safe, supported by the Apache Commons Lang package, and recommended prior to Java8)

Recommend related articles

Hutool date and time series articles

1DateUtil(Time utility class)- current time and current timestamp

2DateUtil(Time tool Class)- Common time types Date, DateTime, Calendar and TemporalAccessor (LocalDateTime) conversions

3DateUtil(Time utility class)- Get various contents for dates

4DateUtil(Time utility class)- Format time

5DateUtil(Time utility class)- Parse the time being formatted

6DateUtil(Time utility class)- Time offset gets

7DateUtil(Time utility class)- Date calculation

8ChineseDate(Lunar Date tools)

9LocalDateTimeUtil({@link LocalDateTime} utility class encapsulation in JDK8+)

10TemporalAccessorUtil{@link TemporalAccessor} Tool class encapsulation

other

To explore the underlying source code at the core of the JDK, you must master native usage

Swastika blog teaches you to understand Java source code date and time related usage