SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM");
SimpleDateFormat fullFormat = new SimpleDateFormat("EE MMM dd, HH:mm:ss")
我有几个经常被调用的代码块,将它们声明为static
变量有意义吗?
在这种情况下,向format()
方法传递动态参数是线程安全的吗?
SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM");
SimpleDateFormat fullFormat = new SimpleDateFormat("EE MMM dd, HH:mm:ss")
我有几个经常被调用的代码块,将它们声明为static
变量有意义吗?
在这种情况下,向format()
方法传递动态参数是线程安全的吗?
不是线程安全的。请使用Joda-Time版本。
或者将它们包装在同步方法中以使其线程安全。
文档清楚地说明了:
日期格式没有进行同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,则必须在外部进行同步。
DateTimeFormatter
是线程安全的,并且可以像SimpleDateFormat
一样工作。引用自JavaDoc:
为了更加明确,定义格式如下也完全没问题:从模式创建的格式化程序可以使用多次,它是不可变的并且是线程安全的。
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
并且在可以被多个线程同时访问的方法中使用它:
String text = date.toString(formatter);
LocalDate date = LocalDate.parse(text, formatter);
DateFormat不是线程安全的。如果多个线程在没有任何同步的情况下使用相同的DateFormat对象,则可能会得到意外的结果。因此,您应该同步访问DateFormat对象,使用ThreadLocal变量或使用另一种日期API,如Joda-Time。
有关如何实现此操作的更多信息,请参阅此博客文章:多线程下的DateFormat
SimpleDateFormat
很容易使用,但也比较复杂。开发者通常编写代码并作为单个用户测试,并且会成功。但在并行和并发请求的情况下,SimpleDateFormat
将是一个复杂的日期格式。
将其暴露为服务或控制器层中的静态变量时,当多个线程同时访问时,它可能会表现异常。
DateFormat API类还建议:“建议为每个线程创建单独的格式实例。如果多个线程并发访问格式,则必须在外部进行同步。”
演示多线程环境下失败的代码。
public final class DateUtils {
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private DateUtils() {
}
public static Date parse(String target) throws ParseException {
return DATE_FORMAT.parse(target);
}
public static String format(Date target) {
return DATE_FORMAT.format(target);
}
private static void testSimpleDateFormatWithThreads() {
ExecutorService executorService = Executors.newFixedThreadPool(5);
final String source = "2019-01-11";
IntStream.rangeClosed(0, 20)
.forEach((i) -> executorService.submit(() -> {
try {
System.out.println(DateUtils.parse(source));
} catch (ParseException e) {
e.printStackTrace();
}
}));
executorService.shutdown();
}
public static void main(String[] args) {
testSimpleDateFormatWithThreads();
}
}
输出 - 这取决于不同的机器,每次运行都会生成不同的输出。
2201年11月11日星期三IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2018年12月31日星期一IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日星期五IST 00:00:00
2019年1月11日IST 00:00:00
2019年1月11日IST 00:00:00
2019年1月11日IST 00:00:00
2019年1月11日IST 00:00:00
2201年1月11日IST 00:00:00
2019年1月11日IST 00:00:00
这是由于DateFormat
->SimpleDateFormat
类内的日历实例变量而导致的错误。
解决方案
使用ThreadLocal
public static final ThreadLocal SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
Java 8,DateTimeFormatter
API
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
static不应该是一个问题。
因为我知道没有关于线程安全的保证,所以你必须检查源代码。即使你得出结论认为它是线程安全的,这在下一个版本可能会改变。 如另一个答案中所述,它们不是线程安全的。
你真的会分配如此大量的线程,以至于给每个线程分配自己的格式是一个问题吗?