简介
本文用示例介绍Java中的SimpleDateFormat的线程安全的操作方法,也介绍SimpleDateFormat为什么不是线程安全的。这两个问题也是面试中常见的问题。
线程不安全实例
package org.example.a; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class Demo{ public static void main(String[] args) { final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Callable<Date> task = new Callable<Date>() { public Date call() throws Exception { return dateFormat.parse("2016-12-18 15:00:34"); } }; // 创建5个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); for (int i = 0; i < 10; i++) { results.add(exec.submit(task)); } exec.shutdown(); // 输出结果 for (Future<Date> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
执行结果(出现异常)
Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "E34" at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:192) at org.example.a.Demo.main(Demo.java:29) Caused by: java.lang.NumberFormatException: For input string: "E34" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2051) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at org.example.a.Demo$1.call(Demo.java:15) at org.example.a.Demo$1.call(Demo.java:13) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "E342" at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:192) Sun Dec 18 15:00:34 CST 2016 at org.example.a.Demo.main(Demo.java:29) Caused by: java.lang.NumberFormatException: For input string: "E342" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2056) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at org.example.a.Demo$1.call(Demo.java:15) at org.example.a.Demo$1.call(Demo.java:13) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "" at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:192) at org.example.a.Demo.main(Demo.java:29) Caused by: java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2051) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at org.example.a.Demo$1.call(Demo.java:15) at org.example.a.Demo$1.call(Demo.java:13) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016
线程不安全原因
SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息,例如sdf.parse(dateStr),sdf.format(date) 诸如此类的方法参数传入的日期相关String,Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题:如果你的SimpleDateFormat是多个thread 之间共享的, 那么也共享这个Calendar引用。假定线程A和线程B都进入了parse(text, pos) 方法, 线程B执行到calendar.clear()后,线程A执行到calendar.getTime(), 那么就会有问题。
线程安全的方案
方案1:new局部变量(开销大)
缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
package org.example.a; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class Demo{ public static void main(String[] args) { Callable<Date> task = new Callable<Date>() { public Date call() throws Exception { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return dateFormat.parse("2016-12-18 15:00:34"); } }; // 创建5个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); for (int i = 0; i < 10; i++) { results.add(exec.submit(task)); } exec.shutdown(); // 输出结果 for (Future<Date> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
执行结果(正常)
Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016
方案2:方法加synchronized(性能差)
package org.example.a; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class Demo{ public static void main(String[] args) { final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Callable<Date> task = new Callable<Date>() { public Date call() throws Exception { synchronized (dateFormat) { return dateFormat.parse("2016-12-18 15:00:34"); } } }; // 创建5个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); for (int i = 0; i < 10; i++) { results.add(exec.submit(task)); } exec.shutdown(); // 输出结果 for (Future<Date> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
执行结果(正常)
Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016
方案3:使用ThreadLocal(推荐)
package org.example.a; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; class DateFormatThreadSafe { public static final ThreadLocal<DateFormat> THREAD_LOCAL = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; } public class Demo{ public static void main(String[] args) { Callable<Date> task = new Callable<Date>() { public Date call() throws Exception { DateFormat dateFormat = DateFormatThreadSafe.THREAD_LOCAL.get(); Date date = dateFormat.parse("2016-12-18 15:00:34"); // 移除ThreadLocal的值,防止内存溢出 DateFormatThreadSafe.THREAD_LOCAL.remove(); return date; } }; // 创建5个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); for (int i = 0; i < 10; i++) { results.add(exec.submit(task)); } exec.shutdown(); // 输出结果 for (Future<Date> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
执行结果(正常)
Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016 Sun Dec 18 15:00:34 CST 2016
方案4:针对JDK1.8(推荐)
说明
如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替Simpledateformatter。
为什么DateTimeFormatter线程安全?
依据1:官方给出的解释:simple beautiful strong immutable thread-safe。
依据2:看DateTimeFormatter代码,它所有字段都是final类型。
实例
package org.example.a; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class Demo{ public static void main(String[] args) { DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); Callable<LocalDateTime> task = new Callable<LocalDateTime>() { public LocalDateTime call() throws Exception { return LocalDateTime.parse("2016-12-18 15:00:34", dtf); } }; // 创建5个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<LocalDateTime>> results = new ArrayList<Future<LocalDateTime>>(); for (int i = 0; i < 10; i++) { results.add(exec.submit(task)); } exec.shutdown(); // 输出结果 for (Future<LocalDateTime> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
执行结果(正常)
2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34 2016-12-18T15:00:34
请先
!