信息学奥赛刷题必备:用二分答案搞定USACO月度开销(附C++代码详解)
2026/6/10 11:23:16
SimpleDateFormat不是线程安全的,根本原因是它内部维护了可变的成员变量:
Calendar对象作为成员变量,用于存储日期解析/格式化过程中的中间状态format()或parse()方法时,会修改这个内部Calendar对象的状态SimpleDateFormat实例时,并发修改会导致内部状态混乱ArrayIndexOutOfBoundsException、NumberFormatException、ParseExceptionimportjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassSimpleDateFormatConcurrencyTest{// 共享的SimpleDateFormat实例privatestaticfinalSimpleDateFormatSDF=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");publicstaticvoidmain(String[]args){ExecutorServiceexecutor=Executors.newFixedThreadPool(10);// 10个线程并发操作同一个SDF实例for(inti=0;i<100;i++){finalintfinalI=i;executor.submit(()->{try{Datedate=newDate(finalI*1000L);Stringformatted=SDF.format(date);Dateparsed=SDF.parse(formatted);System.out.println(Thread.currentThread().getName()+": "+formatted+" -> "+parsed);}catch(Exceptione){System.err.println(Thread.currentThread().getName()+" 出错: "+e.getMessage());}});}executor.shutdown();}}运行结果会出现错误日期或异常,例如:
pool-1-thread-1: 1970-01-01 08:00:03 -> Thu Jan 01 08:00:00 CST 1970 pool-1-thread-2: 1970-01-01 08:00:02 -> Thu Jan 01 08:00:00 CST 1970 pool-1-thread-3: 出错: Unparseable date: "1970-01-01 08:00:008"核心思路:不共享SimpleDateFormat实例,每次需要时创建新对象
publicStringformatDate(Datedate){// 每次调用都创建新实例SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");returnsdf.format(date);}优缺点:
核心思路:为每个线程分配独立的SimpleDateFormat实例,线程间互不干扰
importjava.text.SimpleDateFormat;importjava.util.Date;publicclassThreadSafeDateFormat{// ThreadLocal:每个线程拥有自己的SimpleDateFormat实例privatestaticfinalThreadLocal<SimpleDateFormat>SDF_THREAD_LOCAL=ThreadLocal.withInitial(()->newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"));publicstaticStringformat(Datedate){returnSDF_THREAD_LOCAL.get().format(date);}publicstaticDateparse(StringdateStr)throwsException{returnSDF_THREAD_LOCAL.get().parse(dateStr);}// 关键:使用后移除,避免线程池内存泄漏publicstaticvoidremove(){SDF_THREAD_LOCAL.remove();}}使用方式:
// 推荐使用try-finally确保资源释放try{Stringresult=ThreadSafeDateFormat.format(newDate());}finally{ThreadSafeDateFormat.remove();}优缺点:
remove()核心思路:使用Java 8引入的DateTimeFormatter,它是不可变的线程安全实现
importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;importjava.util.Date;publicclassModernDateTimeFormat{// 不可变的DateTimeFormatter实例,全局共享privatestaticfinalDateTimeFormatterFORMATTER=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 格式化LocalDateTimepublicstaticStringformat(LocalDateTimedateTime){returnFORMATTER.format(dateTime);}// 解析为LocalDateTimepublicstaticLocalDateTimeparse(StringdateStr){returnLocalDateTime.parse(dateStr,FORMATTER);}// 与旧Date类兼容publicstaticStringformat(Datedate){returndate.toInstant().atZone(java.time.ZoneId.systemDefault()).format(FORMATTER);}}优缺点:
LocalDateTime、Instant等)核心思路:通过synchronized或Lock保证同一时间只有一个线程访问实例
publicclassSynchronizedDateFormat{privatestaticfinalSimpleDateFormatSDF=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");publicsynchronizedStringformat(Datedate){returnSDF.format(date);}publicsynchronizedDateparse(StringdateStr)throwsException{returnSDF.parse(dateStr);}}优缺点:
| 方案 | 线程安全 | 性能 | 实现复杂度 | 推荐度 | 适用场景 |
|---|---|---|---|---|---|
| 每次创建新实例 | ✅ | ❌ | 简单 | ⭐ | 低并发场景 |
| ThreadLocal | ✅ | ✅ | 中等 | ⭐⭐⭐ | Java 5-7高并发 |
| DateTimeFormatter | ✅ | ✅ | 中等 | ⭐⭐⭐⭐⭐ | Java 8+所有场景 |
| 同步锁 | ✅ | ❌ | 简单 | ⭐ | 不推荐使用 |
标准答案:
SimpleDateFormat内部维护了可变的Calendar实例作为成员变量format()和parse()方法会修改这个内部状态标准答案:
DateTimeFormatter,它是不可变的线程安全实现ThreadLocal为每个线程分配独立的SimpleDateFormat实例ThreadLocal时要注意内存泄漏,及时调用remove()释放资源标准答案:
ThreadLocal为每个线程创建独立的变量副本SimpleDateFormat实例,不会与其他线程共享// 旧代码:不安全privatestaticfinalSimpleDateFormatOLD_SDF=newSimpleDateFormat("yyyy-MM-dd");// 新代码:线程安全privatestaticfinalDateTimeFormatterNEW_FORMATTER=DateTimeFormatter.ofPattern("yyyy-MM-dd");publicstaticStringsafeFormat(Datedate){try{returnSDF_THREAD_LOCAL.get().format(date);}finally{// 关键:确保资源释放,避免内存泄漏SDF_THREAD_LOCAL.remove();}}// DateTimeFormatter 提供了多种预定义格式DateTimeFormatter.ISO_LOCAL_DATE;// 2023-12-18DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 2023-12-18T15:30:45DateTimeFormatter.ISO_INSTANT;// 2023-12-18T07:30:45Z| 异常类型 | 产生原因 | 解决方案 |
|---|---|---|
| ArrayIndexOutOfBoundsException | SimpleDateFormat内部数组越界 | 改用DateTimeFormatter或ThreadLocal |
| NumberFormatException | 解析过程中数字格式错误 | 确保日期字符串格式正确,使用安全的解析方式 |
| DateTimeParseException | DateTimeFormatter解析失败 | 捕获异常并返回默认值或错误信息 |
| ParseException | SimpleDateFormat解析失败 | 同上 |
SimpleDateFormat内部存在可变状态,并发访问导致竞态条件DateTimeFormatter,Java 5-7 推荐ThreadLocalThreadLocal实现了线程安全与性能的最佳平衡ThreadLocal时务必及时调用remove()通过以上方案,可以彻底避免SimpleDateFormat的并发问题,确保日期格式化在多线程环境下的安全性和高效性。