在項目中或多或少會用到日期格式。如果在單線程中,可以不用考慮使用的格式化類是否線程安全,但是在多線程,並發執行時,就要考慮線程同步的問題了。
下面提供四中解決方式,並簡單說明一下優缺點(看注釋)
ConcurrentDateFormat 和 ThreadLocalDateFormat 是自己封裝的
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 驗證 SimpleDateFormat 是線程不安全的
*/
public class DateFormat {
/**
* 只創建一份,避免頻繁地創建對象和銷毀對象
* 單線程下可以不出錯
* 多線程下則不安全, 不同的線程會對不同日期字符串進行解析,
* 會出現線程 A 解析到一半被掛起,線程 B 運行時將 A 的解析到一半的字符串覆蓋掉,
* 這樣輪到 A 運行時會解析失敗,或者使用了 B 的字符串
*/
private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 線程安全的 DateTimeFormatter
* 推薦使用,因為該類是不可變的,並且是線程安全的
*/
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Test
public void formatTest() {
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++){
service.execute(new Runnable() {
@Override
public void run() {
String dateStr = "2019-04-16 10:26:30";
// 解決方法
// 1、可以只在需要時創建對象,也可以避免錯誤,但是頻繁創建與銷毀會導致額外的開銷,性能低
// SimpleDateFormat newInNeed = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(int j = 0; j < 10; j++){
//
// try {
// 直接使用不但運行結果錯誤,最后還會拋出 NumberFormatException: 異常
// System.out.println(format.parse(dateStr));
// 2、使用加了 synchronized 的同步方法,但是並發量高時,性能影響大,線程阻塞
// System.out.println(ConcurrentDateFormat.parse(dateStr));
// 3、使用 ThreadLocal 來解決,比較優雅的一種做法
// System.out.println(ThreadLocalDateFormat.parse(dateStr));
// } catch (ParseException e) {
// e.printStackTrace();
// }
// 4、使用DateFormatter,該類是線程安全的,可以放心使用
LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dtf);
System.out.println(localDateTime);
}
}
});
}
}
}
ConcurrentDateFormat.java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 通過 synchronized 做一個線程安全的DataFormat
*/
public class ConcurrentDateFormat {
/**
* 依舊只創建一個 SimpleFormat 對象
*/
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 采用加鎖的方式,防止同步問題
public static String format(Date date){
synchronized (sdf){
return sdf.format(date);
}
}
public static Date parse(String date) throws ParseException {
synchronized (sdf){
return sdf.parse(date);
}
}
}
ThreadLocalDateFormat.java
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 通過 ThreadLocal 為每個線程做一份變量副本,實現線程安全
*/
public class ThreadLocalDateFormat {
/**
* ThreadLocal 提供一種 lombda 構造方式
* 返回此線程局部變量的當前線程的“初始值”。線程第一次使用 get() 方法訪問變量時將調用此方法,
* 但如果線程之前調用了 set(T) 方法,則不會對該線程再調用 initialValue 方法。通常,此方法
* 對每個線程最多調用一次,但如果在調用 get() 后又調用了 remove(),則可能再次調用此方法。
*/
private static ThreadLocal<DateFormat> threadLocal
= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date parse(String date) throws ParseException {
System.out.println(date);
return threadLocal.get().parse(date);
}
public static String format(Date date){
return threadLocal.get().format(date);
}
}
原文:https://blog.csdn.net/innocent_jia/article/details/89331045
