Detailed usage of SimpleDateFormat in Java

In daily development, we often use time, and we have many ways to get time in Java code. However, the format of the time obtained by different methods is different. At this time, a formatting tool is needed to display the time in the format we need.

The most common way is to use the SimpleDateFormat class. This is a class that seems to have relatively simple functions, but if it is used improperly, it may cause serious problems.

In the Alibaba Java Development Manual, there are clear regulations as follows:

Then, this article focuses on the usage and principle of SimpleDateFormat to analyze how to use it in the correct posture.

SimpleDateFormat usage

SimpleDateFormat is a tool class provided by Java for formatting and parsing dates. It allows formatting (date -> text), parsing (text -> date) and normalization. SimpleDateFormat enables the selection of any user-defined date-time format pattern.

In Java, you can use the format method of SimpleDateFormat to convert a Date type into a String type, and you can specify the output format.

// Date转String
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr = sdf.format(data);
System.out.println(dataStr);

The above code, the conversion result is: 2018-11-25 13:00:00, the date and time format is specified by the "date and time pattern" string. If you want to convert to another format, just specify a different time mode.

In Java, you can use the parse method of SimpleDateFormat to convert a String type into a Date type.

// String转Data
System.out.println(sdf.parse(dataStr));

Date and time pattern expression method

When using SimpleDateFormat, you need to describe the time elements by letters and assemble them into the desired date and time patterns. The correspondence table between commonly used time elements and letters is as follows:

![-w717][1]

Pattern letters are usually repeated, the number of which determines their exact representation. The following table shows the commonly used output formats.

![-w535][2]

Output time in different time zones

A time zone is an area of ​​the globe that uses the same definition of time. In the past, people determined the time by observing the position of the sun (hour angle), which made the time of places with different longitudes different (local time). In 1863, the concept of time zone was used for the first time. Time zones solve this problem in part by setting a standard time for a region.

Countries in the world are located in different positions on the earth, so the sunrise and sunset times of different countries, especially those with a large east-west span, must be different. These deviations are known as jet lag.

Today the world is divided into 24 time zones. In practice, one country or one province often spans two or more time zones at the same time. In order to take care of administrative convenience, one country or one province is often grouped together. Therefore, the time zone is not strictly divided by the north-south straight line, but by natural conditions. For example, China has a vast territory and spans almost 5 time zones. However, for the convenience and simplicity of use, in fact, only the standard time of the East 8th time zone, that is, Beijing time, prevails.

Since the time in different time zones is different, even in different cities in the same country, the time may be different. Therefore, when you want to get the time in Java, you must pay attention to the time zone.

By default, if not specified, the time zone where the current computer is located will be used as the default time zone when creating the date, which is why we can get the current time in China just by using it new Date().

So, how to get time in different time zone in Java code? SimpleDateFormat can achieve this function.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(Calendar.getInstance().getTime()));

The above code, the conversion result is: 2018-11-24 21:00:00 . The time in China is 13:00 on November 25th, and the time in Los Angeles is 16 hours behind Beijing time in China (this is also related to winter and summer time, so I won’t go into details).

If you are interested, you can also try to print the time in New York, USA (America/New_York). New York time is 2018-11-25 00:00:00. New York time is 13 hours ahead of Beijing time in China.

Of course, this is not the only way to display other time zones, but this article mainly introduces SimpleDateFormat, and other methods will not be introduced for the time being.

SimpleDateFormat thread safety

Since SimpleDateFormat is commonly used, and in general, the time display mode in an application is the same, so many people are willing to define SimpleDateFormat in the following way:

public class Main {

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

    public static void main(String[] args) {
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
    }
}

This definition method has great security risks.

problem reproduction

Let's look at a piece of code, the following code uses the thread pool to perform time output.

   /** * @author Hollis */ 
   public class Main {

    /**
     * 定义一个全局的SimpleDateFormat
     */
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 使用ThreadFactoryBuilder定义一个线程池
     */
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    /**
     * 定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
     */
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                    //时间增加
                    calendar.add(Calendar.DATE, finalI);
                    //通过simpleDateFormat把时间转换成字符串
                    String dateString = simpleDateFormat.format(calendar.getTime());
                    //把字符串放入Set中
                    dates.add(dateString);
                    //countDown
                    countDownLatch.countDown();
            });
        }
        //阻塞,直到countDown数量为0
        countDownLatch.await();
        //输出去重后的时间个数
        System.out.println(dates.size());
    }
}

The above code is actually relatively simple and easy to understand. It is to loop one hundred times, and add a number of days to the current time every time it loops (this number of days changes with the number of loops), and then put all the dates into a thread-safe Set with deduplication function , and then output the number of elements in the Set.

The above example I purposely wrote is a little more complicated, but I added comments almost all of it. This involves [thread pool creation][3], [CountDownLatch][4], lambda expressions, thread-safe HashSet and other knowledge. Interested friends can find out one by one.

Under normal circumstances, the output of the above code should be 100. But the actual execution result is a number less than 100.

The reason is that SimpleDateFormat, as a non-thread-safe class, is used as a shared variable in multiple threads, which leads to thread safety issues.

There is also a clear statement on this point in Chapter 1, Section 6 of the Alibaba Java Development Manual - Concurrent Processing:

So, let's take a look at why and how to solve it.

thread unsafe reason

Through the above code, we found that using SimpleDateFormat in concurrent scenarios will have thread safety issues. In fact, the JDK documentation has clearly stated that SimpleDateFormat should not be used in multi-threaded scenarios:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

Then analyze why this kind of problem occurs, and how is the bottom layer of SimpleDateFormat realized?

Let's follow the implementation of the format method in the SimpleDateFormat class to find out.

![][5]

The format method in SimpleDateFormat will use a member variable calendar to save the time during execution. This is actually the crux of the problem.

Because when we declare SimpleDateFormat, we use the static definition. Then this SimpleDateFormat is a shared variable, and then the calendar in SimpleDateFormat can be accessed by multiple threads.

Assume that thread 1 has just finished executing calendar.setTimeand set the time to 2018-11-11. Before it finishes executing, thread 2 executes again calendar.setTimeand changes the time to 2018-12-12. At this time, thread 1 continues to execute, and calendar.getTimethe time obtained is after thread 2 has changed.

In addition to the format method, the parse method of SimpleDateFormat has the same problem.

So, don't use SimpleDateFormat as a shared variable.

How to solve

I have introduced the problems of SimpleDateFormat and the reasons for the problems, so is there any way to solve this problem?

There are many solutions, here are three more commonly used methods.

use local variables

for (int i = 0; i < 100; i++) {
    //获取当前时间
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        // SimpleDateFormat声明成局部变量
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //时间增加
        calendar.add(Calendar.DATE, finalI);
        //通过simpleDateFormat把时间转换成字符串
        String dateString = simpleDateFormat.format(calendar.getTime());
        //把字符串放入Set中
        dates.add(dateString);
        //countDown
        countDownLatch.countDown();
    });
}

SimpleDateFormat becomes a local variable, so it will not be accessed by multiple threads at the same time, thus avoiding thread safety issues.

Add sync lock

In addition to changing to local variables, there is another method that you may be more familiar with, which is to lock shared variables.

for (int i = 0; i < 100; i++) {
    //获取当前时间
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        //加锁
        synchronized (simpleDateFormat) {
            //时间增加
            calendar.add(Calendar.DATE, finalI);
            //通过simpleDateFormat把时间转换成字符串
            String dateString = simpleDateFormat.format(calendar.getTime());
            //把字符串放入Set中
            dates.add(dateString);
            //countDown
            countDownLatch.countDown();
        }
    });
}

By locking, multiple threads are queued and executed sequentially. Avoids thread safety issues caused by concurrency.

In fact, there is still room for improvement in the above code, that is, the granularity of the lock can be set smaller, and only simpleDateFormat.formatthis locked, which is more efficient.

Use ThreadLocal

The third way is to use ThreadLocal. ThreadLocal can ensure that each thread can get a single SimpleDateFormat object, so naturally there will be no competition problem.

/**
 * 使用ThreadLocal定义一个全局的SimpleDateFormat
 */
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

Using ThreadLocal to implement is actually a bit similar to the idea of ​​caching. Each thread has an exclusive object, which avoids frequent object creation and multi-thread competition.

Of course, the above code also has room for improvement, that is, in fact, the creation process of SimpleDateFormat can be changed to lazy loading. I won't go into details here.

Use DateTimeFormatter

If it is a Java8 application, you can use DateTimeFormatter instead of SimpleDateFormat, which is a thread-safe formatting tool class. As stated in the official documentation, this class is simple beautiful strong immutable thread-safe.

//解析日期
String dateStr= "2016年10月25日";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date= LocalDate.parse(dateStr, formatter);

//日期转换为字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
String nowStr = now .format(format);
System.out.println(nowStr);

Summarize

This article introduces the usage of SimpleDateFormat. SimpleDateFormat can mainly convert between String and Date, and can also convert time into different time zones for output. At the same time, it is mentioned that SimpleDateFormat cannot guarantee thread safety in concurrent scenarios, and developers need to ensure its safety.

The main methods are changing to local variables, using synchronized to lock, using Threadlocal to create a separate thread for each thread, etc.

I hope that through this article, you can be more handy when using SimpleDateFormat.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132307231