1. 前言
主要内容 :
-
不可变类的使用
-
不可变类设计
-
无状态类设计
2. 日期转换的问题
参考文献
- https://blog.csdn.net/csdn_ds/article/details/72984646
SimpleDateFormat问题
多线程下SimpleDateFormat
会出现并发问题 NumberFormatException
package cn.knightzz.unsafe;
import lombok.extern.slf4j.Slf4j;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@SuppressWarnings("all")
@Slf4j(topic = "c.SimpleDateFormatTest")
public class SimpleDateFormatTest {
public static void main(String[] args) {
// 多个线程通过使用解析字符
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
log.debug("{}" , sdf.parse("1921-04-21"));
} catch (ParseException e) {
log.debug("{}" , e);
}
}, "t" + i).start();
}
}
}
SimpleDateFormat源码分析
原因很简单 :
SimpleDateFormat 底层是使用一个 Calendar 对象来存储日期信息, 并发环境下, 多个线程会共享同一个Calendar对象
在解析方法 parsedDate = calb.establish(calendar).getTime();
上面可以看到啊, 在解析字符串的是偶, 是先 clear 然后再set, 多线程下肯定会出现覆盖的问题, 比如 , t1线程刚执行完set()
然后线程上下文切换, t2线程直接给 clear()
然后切换到t1
线程 , 此时结果就成空了…
解决方法-同步锁
// 多个线程通过使用解析字符
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
synchronized (sdf){
try {
log.debug("{}" , sdf.parse("1921-04-21"));
} catch (ParseException e) {
log.debug("{}" , e);
}
}
}, "t" + i).start();
}
这样虽能解决问题,但带来的是性能上的损失
解决方法-DateTimeFormatter
如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改啊!这样的对象在
Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类:DateTimeFormatter
// 多个线程通过使用解析字符
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
LocalDate date = dtf.parse("2020-02-23" , LocalDate::from);
log.debug("==> {}" , date);
}, "t" + i).start();
}
3. 不可变设计
设计一个不可变的类
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
// ...
}
- 所有私有化属性不提供set方法
- 发现该类、类中所有属性都是 fifinal 的
- 属性用 final 修饰保证了该属性是只读的,不能修改
- 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性
4.保护性拷贝
定义 : 通过拷贝副本对象来避免变量共享
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
以 substring
为例, 它是先进行安全性校验, 然后新建了一个String对象, 而不是修改原有的Stirng, 这样可以防止多个变量共享导致并发安全问题
其内部是调用 String 的构造方法创建了一个新字符串,再进入这个构造看看,是否对 fifinal char[] value 做出
了修改:
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。这种通过创建副本对象来避
免共享的手段称之为保护性拷贝