不可变类的应用
SimpleDateFormat是作日期字符串格式化转换的类,但是它是非线程安全的,在多线程场景会出现问题
@Slf4j
public final class Demo{
public static void main(String[] args){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
log.debug("{}",sdf.parse("2001-01-01"));
} catch (ParseException e) {
e.printStackTrace();
}
},"t"+i).start();
}
}
}
要解决这个问题,可以使用synchornized对象锁:
@Slf4j
public final class Demo{
public static void main(String[] args){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
synchronized (sdf){
log.debug("{}",sdf.parse("2001-01-01"));
}
} catch (ParseException e) {
e.printStackTrace();
}
},"t"+i).start();
}
}
}
或者使用DateTimeFormat类,这个类底层就是不可变的,即状态无法被修改,所以是线程安全的
@Slf4j
public final class Demo{
public static void main(String[] args){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(()->{
log.debug("{}",dtf.parse("2001-01-01"));
},"t"+i).start();
}
}
}
不可变类的设计-String类
final的使用
- 修饰了属性,保证属性是只读的不能被修改,所以不会出现多线程修改导致读取错误数据的问题,保证了线程安全
- 修饰了类,表示该类不可被继承,防止开发者继承该类无意间破坏了该类的不可变性
保护性拷贝
String类通过char数组的构造器,内部是复制了一份原char数组的内容,将复制出来的新数组赋值给String底层的char数组,这种思想叫做保护性拷贝;防止外部对char数组的修改,影响字符串内部的不可变性
String内部有多处体现了保护性拷贝的思想,如substring这样在原有基础上作字符串处理的方法:
final原理
设置final变量
public class Demo{
private final int num = 0;
}
final变量的赋值也会通过putfield指令来完成,但是在putfield指令之后会加入写屏障,保证在其他线程读到它的值时不会出现为0的情况
写屏障
- 保证写屏障之前的指令不会被重排序到写屏障之后,保证有序性
- 写屏障之前的修改和赋值操作,在加入写屏障之后会同步到主存中,保证可见性
获取final变量
@Slf4j
public final class Demo{
private static final int num = 20;
public static void main(String[] args) {
System.out.println(num);
}
}
如果不加final:
无状态
比如设计Servlet时,为了保证其线程安全,都会有这样的建议:不要为Servlet设置成员变量,这样的设计思想,即为无状态;因为成员变量也可称为状态信息,如果没有成员变量,多线程情况下就不会有对象读写的情况发生