7.1 日期转换的问题
问题提出,下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的,有很大几率出现 java.lang.NumberFormatException
或者出现不正确的日期解析结果。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.error("{}", e);
}
}).start();
}
有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果,例如:
解决方案
方案一:加锁
方案二:使用DateTimeFormatter
思路 - 不可变对象
如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改,这样的对象在 Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类DateTimeFormatter:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LocalDate date = dtf.parse("2018-10-01", LocalDate::from);
log.debug("{}", date);
}).start();
}
7.2 不可变设计
另一个大家更为熟悉的 String 类也是不可变的,以它为例,说明一下不可变类设计的要素
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
// ...
}
final 的使用
发现该类、类中所有属性都是 final 的
属性用 final 修饰保证了该属性是只读的,不能修改
类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性
保护性拷贝
String的构造方法,传递字符串数组,通过数组拷贝实现数据的安全性,这也是一种保护性拷贝。如果其他线程都用同一个数组,那么其他线程修改了那数据就不能保证数据的安全性。
但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如 substring 等,那么下面就看一看这些方法是 如何实现的,就以 substring 为例:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.l