SimpleDateFormat非线程安全
前言:在一次ReviewBoard上,被老大指出SimpleDateFormat存在线程安全的问题,所以特地将其提出来看看,并提出其解决方案。
1.SimpleDateFormat线程不安全的表现
这里通过代码来复现SimpleDateFormat线程不安全的表现,具体代码如下:
1 public class SimpleDateFormatTest extends Thread { 2 3 private SimpleDateFormat sdf; 4 5 private String dateString; 6 7 public SimpleDateFormatTest(SimpleDateFormat sdf, String dateString) { 8 this.sdf = sdf; 9 this.dateString = dateString; 10 } 11 12 @Override 13 public void run() { 14 try { 15 Date date = sdf.parse(dateString); 16 System.out.println(date.toString()); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 } 21 22 public static void main(String[] args) { 23 24 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 25 26 String[] dateStringArray = {"2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01"}; 27 28 SimpleDateFormatTest[] threadArray = new SimpleDateFormatTest[5]; 29 30 for (int i = 0; i < 5; i++) { 31 threadArray[i] = new SimpleDateFormatTest(sdf, dateStringArray[i]); 32 } 33 for (int i = 0; i < 5; i++) { 34 threadArray[i].start(); 35 } 36 } 37 }
注:运行5个线程来进行时间字符串的转换。
运行结果如下:
运行结果抛出了异常,通过查看SimpleDateFormat知道了原因:
SimpleDateFormat源码的注解中明确指出在进行日期转换的时候是不同步的,也就会出现线程安全问题,因此建议为每个线程都创建一个SimpleDateFormat对象,如果在多线程环境中要使用一个SimpleDateFormat对象,就必须保证其同步。
修改以上源码为每个线程都创建一个SimpleDateFormat对象,可解决该问题。源码如下:
1 public class SimpleDateFormatTest extends Thread { 2 3 private SimpleDateFormat sdf; 4 5 private String dateString; 6 7 public SimpleDateFormatTest(SimpleDateFormat sdf, String dateString) { 8 this.sdf = sdf; 9 this.dateString = dateString; 10 } 11 12 @Override 13 public void run() { 14 try { 15 Date date = sdf.parse(dateString); 16 System.out.println(date.toString()); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 } 21 22 public static void main(String[] args) { 23 24 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 25 26 String[] dateStringArray = {"2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01"}; 27 28 SimpleDateFormatTest[] threadArray = new SimpleDateFormatTest[5]; 29 30 for (int i = 0; i < 5; i++) { 31 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 32 threadArray[i] = new SimpleDateFormatTest(sdf, dateStringArray[i]); 33 } 34 for (int i = 0; i < 5; i++) { 35 threadArray[i].start(); 36 } 37 } 38 }
运行结果如下(正常运行为出现任何线程安全问题):
2.利用FastDateFormat/DateFormatUtils替代SimpleDateFormat
Apache提供的这两个类在时间上的转换是线程安全的,因此可以用其对SimpleDateFormat进行替代。
下面用FastDateFormat进行替代(使用DateFormatUtils效果一样)具体代码如下:
1 public class SimpleDateFormatTest extends Thread { 2 3 // private SimpleDateFormat sdf; 4 5 private FastDateFormat fastDateFormat; 6 7 private String dateString; 8 9 public SimpleDateFormatTest(FastDateFormat fdf, String dateString) { 10 this.fastDateFormat = fdf; 11 this.dateString = dateString; 12 } 13 14 @Override 15 public void run() { 16 try { 17 Date date = fastDateFormat.parse(dateString); 18 System.out.println(date.toString()); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23 24 public static void main(String[] args) { 25 26 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 27 28 FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd"); 29 String[] dateStringArray = {"2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01", "2001-01-01"}; 30 31 SimpleDateFormatTest[] threadArray = new SimpleDateFormatTest[5]; 32 33 for (int i = 0; i < 5; i++) { 34 threadArray[i] = new SimpleDateFormatTest(fastDateFormat, dateStringArray[i]); 35 } 36 for (int i = 0; i < 5; i++) { 37 threadArray[i].start(); 38 } 39 } 40 }
运行结果如下,在多线程环境中未出现线程安全问题:
总结
通过以上实验,在下次使用SimpleDateFormat的时候,需要特别注意,最好是使用FastDateFormat,或者DateFormatUtils进行替代,防止在多线程环境中出现线程安全问题,具体非线程安全问题,继续研究源码。
补充:阅读FastDateFormat的源码可知,其内部变量都是final域的,保证了线程安全。
by Shawn Chen,2018.11.21日,上午。