SimpleDateFormat介绍
SimpleDateFormat用于以区域设置敏感的方式格式化和解析日期。 它允许格式化(日期文本),解析(文本日期)和归一化。
日期和时间格式由日期和时间模式字符串指定。 在日期和时间模式字符串中,从’A’到’Z’和从’a’到’z’的非引号的字母被解释为表示日期或时间字符串的组件的模式字母。 可以使用单引号( ’ )引用文本,以避免解释。 "’’"代表单引号。 所有其他字符不被解释; 在格式化过程中,它们只是复制到输出字符串中,或者在解析过程中与输入字符串匹配。
例子
以下示例显示如何在美国地区中解释日期和时间模式。 给定的日期和时间是2001-07-04 12:08:56当地时间在美国太平洋时间时区。
什么情况下static定义的会出现错误
public class Demo {
//定义一个静态SimpleDateFormat
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//线程数量
static int num = 100;
//定义同步辅助工具,主线程等待所有子线程执行完毕
private static CountDownLatch countDownLatch = new CountDownLatch(num);
public static void main(String[] args) throws InterruptedException {
//一个线程安全的set,去重
Set<String> dateSet = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < num; i++) {
int j = i;
new Thread() {
@Override
public void run() {
//获取使用默认时区和地区的日历。返回的日历基于默认时区中的当前时间
Calendar calendar = Calendar.getInstance();
//每次循环在当天是的时间上+i天,理想状态下最后时间是当前时间的100天后
calendar.add(Calendar.DATE, j);
//格式化
String dateString = simpleDateFormat.format(calendar.getTime());
//把字符串放入Set中
dateSet.add(dateString);
//countDown
countDownLatch.countDown();
}
}.start();
}
//阻塞,直到countDown数量为0
countDownLatch.await();
//输出去重后的时间个数
System.out.println(dateSet.size());
}
}
这段代码理想状态下最后输出的为100,但是结果并不是,为什么会这样呢?
在JDK官方文档中这样描述:
SimpleDateFormat 并不是一个线程安全的类。
看下SimpleDateFormat的format是如何实现的
SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。
由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。
假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11,还没等执行完,线程2又执行了calendar.setTime把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的calendar.getTime得到的时间就是线程2改过之后的。
除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。
所以,不要把SimpleDateFormat作为一个共享变量使用。