本文主要是介绍SimpleDateFormat两个著名的坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 坑1:定义的static的SimpleDateFormat 可能出现线程安全问题
- 坑2:当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 并不报错,而是返回其他日期
坑1:定义的static的SimpleDateFormat 可能出现线程安全问题
SimpleDateFormat是线程不安全的类,定义为static对象,会有数据同步风险。通过源码可以看出,SimpleDateFormat内部有一个Calendar对象,在日期转字符串或字符串转日期的过程中,多线程共享时有非常高的概率产生错误,推荐的方式之一时使用ThreadLocal,让每个线程单独拥有这个对象。
示例代码:
public class SimpleDateFormatterTest {static ExecutorService threadPool = Executors.newFixedThreadPool(20);static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {for(int i=0;i<200;i++){threadPool.execute(()->{try {System.out.println(format.parse("2021-07-09 16:29:21"));} catch (ParseException e) {e.printStackTrace();};});}}
}
运行程序后大量报错,且没有报错的输出结果也不正确
为什么会出现上述问题呢?
SimpleDateFormat 的作用是的另一解析和格式化日期时间的模式,这看起来是一次性工作。应该复用,但它的解析和格式化的操作是非线程安全的。
*SimpleDateFormat 继承 DateFormat ,DateFormat 有一个成员变量 calendar。
SimpleDateFormat 的parse 方法如下:
public Date parse(String source) throws ParseException{ParsePosition pos = new ParsePosition(0);Date result = parse(source, pos);if (pos.index == 0)throw new ParseException("Unparseable date: \"" + source + "\"" ,pos.errorIndex);return result;}
最终会调用CalendarBuilder的establish 方法来构建Calendar
parsedDate = calb.establish(calendar).getTime();
establish 方法内部是先清空 Calendar再构建Calendar,整个的操作没有加锁
Calendar establish(Calendar cal) {......cal.clear();......return cal;}```如果多线程在并发操作一个Calendar, 可能会产生一个线程还没来得及处理Calendar 就被另外一个线程清空了,所以会出现解析错误和异常。那么怎么解决呢?* 每次使用时new一个SimpleDateFormat 的 实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其他引用,又需要回收,开销会很大。
* 可以使用syncheronized 对SimpleDtaFormat实例进行同步
* 【推荐】 使用ThreadLocl,这样每个线程只需要使用一个SimpleDateFormate实例,这相比第一种方式 节省了对象的创建销毁开销,并且不需要使多个线程同步。
* 使用Java8的DateTimeFormatter 类下面是用ThreadLocal实现的示例:
```java
public class SimpleDateFormatterTest {static ExecutorService threadPool = Executors.newFixedThreadPool(20);private static final ThreadLocal<SimpleDateFormat> SIMPLEDATEFORMAT_THREADLOCAL = new ThreadLocal<SimpleDateFormat>(){@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) throws InterruptedException {for(int i=0;i<200;i++){threadPool.execute(()->{try {System.out.println(SIMPLEDATEFORMAT_THREADLOCAL.get().parse("2021-07-09 16:29:21"));} catch (ParseException e) {e.printStackTrace();};});}threadPool.shutdown();threadPool.awaitTermination(10, TimeUnit.SECONDS);}
}
坑2:当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 并不报错,而是返回其他日期
public static void main(String[] args) throws InterruptedException, ParseException {String dateString = "20210908";SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");System.out.println(simpleDateFormat.parse(dateString));}
结果:
Wed Aug 01 00:00:00 CST 2096
竟然输出了 2096年了
对于上面的两个坑,我们可以使用java8的DateTimeFormatter 来避免
1) 解决第一个坑
public class DateTimeFormatterTest {static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");static ExecutorService threadPool = Executors.newFixedThreadPool(20);public static void main(String[] args) throws InterruptedException {for(int i=0;i<200;i++){threadPool.execute(()->{System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));});}threadPool.shutdown();threadPool.awaitTermination(10, TimeUnit.SECONDS);}}
- 解决第二个坑
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));
此时会直接报错,而不会出现不正确的结果;
DateTimeFormatter 是线程安全的,可以定义为static 使用,最后,DateTimeFormatter 的解析比较严格,需要解析的字符串和格式不匹配时,会直接报错。而不是错误的解析。
这篇关于SimpleDateFormat两个著名的坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!