public static void main(String[] args) {
// DateTimeFormatter是不可变对象,线程安全
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
log.debug("{}", formatter.parse("1951-04-21"));
}).start();
}
//test();
}
// SimpleDateFormat是线程不安全的
private static void test() {
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();
}
}
// 包装类,字符串,BigDecimal,BigInteger是不可变类,都体现了享元模式,是线程安全的
class AccountCas implements Account{
private AtomicReference<BigDecimal> balance;
public AccountCas(BigDecimal balance){
this.balance = new AtomicReference<>(balance);
}
public BigDecimal getBalance(){
return balance.get();
}
public void withdraw(BigDecimal amount){
while(true){
// 每个单个方法都是线程安全的,但是多个方法的组合不是线程安全的
// 上一个线程还没有来得及减,当前线程拿到的又是之前的值,此时就会有线程安全
// 所以要使用AtomicReference进行保护
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
如何保证不可变性
类是final的:不可继承,所有方法不会被覆盖,防止子类无意间破环不可变性
属性是final的:只读,不可修改
// String类中
// 保护性拷贝
// 重新生成一个副本(深拷贝),而不是直接指向(浅拷贝),避免共享
this.value = Arrays.copyOf(value, value.length);
// String虽然避免了共享,但是对象创建太频繁
享元模式
最小化内存使用
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
// Boolean,Byte,short,Integer等包装类提供了valueOf方法
// Byte,Short,Long缓存范围都是-128-127
// Character缓存范围是0-127
// Integer也是-128-127,最小值不能变,但最大值可以通过调整虚拟机参数
// -Djava.lang.Integer.IntegerCache.high
// Boolean缓存了TRUE和FALSE
final原理
在赋值之后加入写屏障
写屏障之前的指令不会被重排序
写屏障之前的赋值操作会被同步到主存
static final int A = 10;
static final int B = Short.MAX_VALUE + 1;
读取final变量时,是直接将其值10(复制到方法的栈中,较大会复制到常量池中)拿到(BIPUSH 10, LDC 32768)
如果不加final,会用getstatic指令获取变量的值(GETSTATIC cn/itcast/n5/TestFinal.A)堆中
Servlet没有成员变量,这种没有成员变量的类也是线程安全的
final
享元模式在String中的应用