SimpleDateFormat 线程不安全
众所周知 SimpleDateFormat 线程不安全,不少朋友被其坑过。
下面是 stackoverflow 的文章 why-is-javas-simpledateformat-not-thread-safe 中的栗子。
1public class ExampleClass { 2 3 private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)"); 4 private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy"); 5 6 public static void main(String[] args) { 7 ExecutorService executor = Executors.newFixedThreadPool(100); 8 while (true) { 9 executor.submit(new Runnable() { 10 @Override 11 public void run() { 12 workConcurrently(); 13 } 14 }); 15 } 16 } 17 18 public static void workConcurrently() { 19 Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015"); 20 Timestamp startAdvDate = null; 21 try { 22 if (matcher.find()) { 23 String dateCreate = matcher.group(1); 24 startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime()); 25 } 26 } catch (Throwable th) { 27 th.printStackTrace(); 28 } 29 System.out.print("OK "); 30 } 31}
And result :
1OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK java.lang.NumberFormatException: For input string: ".201519E.2015192E2" 2at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) 3at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) 4at java.lang.Double.parseDouble(Double.java:538) 5at java.text.DigitList.getDouble(DigitList.java:169) 6at java.text.DecimalFormat.parse(DecimalFormat.java:2056) 7at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) 8at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 9at java.text.DateFormat.parse(DateFormat.java:364) 10at com.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.java:37) 11at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.java:25) 12at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) 13at java.util.concurrent.FutureTask.run(FutureTask.java:266) 14at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 15at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 16at java.lang.Thread.run(Thread.java:745)
解决方案
-
每次
new(实例化)SimpleDateFormat。 -
利用
ThreadLocal确保每个线程都可以得到单独的一个SimpleDateFormat。public class DateUtil { private static final ThreadLocal<SimpleDateFormat> local = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
1public static String format(Date date) { 2 return local.get().format(date); 3} 4 5public static Date parse(String dateStr) throws ParseException { 6 return local.get().parse(dateStr); 7}}
-
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3-version}</version> </dependency>commons-lang3中的FastDateFormat。
性能比拼
性能咋样,jmh 来一把,源码见:https://github.com/lets-mica/mica-jmh
1# JMH version: 1.21 2# VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM, 25.221-b11 3 4Benchmark Mode Cnt Score Error Units 5newSimpleDateFormat thrpt 5 114072.841 ± 989.135 ops/s 6threadLocal thrpt 5 348207.331 ± 46014.175 ops/s 7fastDateFormat thrpt 5 434391.553 ± 7799.593 ops/s
结果:fastDateFormat 得分最高。当然你觉得这样就完了?
利用 Instant + DateTimeFormatter
在 mica 1.2.1 中我们利用 Instant 来中转 Date 使用 DateTimeFormatter 格式化。
1public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); 2 3public String format(Date date) { 4 return DATETIME_FORMATTER.format(date.toInstant()); 5}
注意:DateTimeFormatter 格式化 Instant 需要指定时区。
jdk 8 压测结果
1# JMH version: 1.21 2# VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM, 25.221-b11 3 4Benchmark Mode Cnt Score Error Units 5fastDateFormat thrpt 5 417338.980 56543.104 ops/s 6toInstantFormat thrpt 5 371028.709 72059.917 ops/s
jdk 11 压测结果
1# JMH version: 1.21 2# VM version: JDK 11.0.4, OpenJDK 64-Bit Server VM, 11.0.4+10-b304.69 3 4Benchmark Mode Cnt Score Error Units 5fastDateFormat thrpt 5 384637.138 7402.690 ops/s 6toInstantFormat thrpt 5 487482.436 12490.986 ops/s
结论
使用 DateTimeFormatter + Instant 在 java8 下和 commons-lang3 中的 FastDateFormat 已经接近 ,高版本的 jdk 表现突出。 如果你在使用高版本的 jdk 或者考虑后期升级到高版本的 JDK,该方式都是一个不错的选择。
欢迎关注我们的公众号:如梦技术,精彩内容每日推送。