1、日期转换的问题
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
log.info("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.info("{}", e);
}
}).start();
}
}
}
以上代码很容易出现如下错误,原因是 SimpleDateFormat 不是线程安全的,是一个可变类,即其内部状态是可变的
想要解决这个问题,常规做法当然是加锁,但是加锁必然带来性能的降低
2、不可变类的使用
如果能够保证一个对象其内部状态(属性)不能够被修改,那么它就是线程安全的,因为不存在并发修改啊!其实这样的对象在 Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类 DateTimeFormatter ,它便是一个不可变类,所以是一个线程安全的类
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LocalDate date = dtf.parse("2022-02-11", LocalDate::from);
log.info("{}", date);
}).start();
}
3、不可变类的设计
我们以 String 类为例,说明一下不可变设计的要素,观察源码不难发现,该类、类中所有属性都是 final 的:
- 属性用 final 修饰保证了该属性是只读的,不能修改
- 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性
而且,其中的所有修改方法,如 substring 等,其内部也是重新创建的新的 String ,并对原始字符数组做复制操作而已,这种通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】