Immutable不变模式
并发问题是由于不同线程共同操作共享内存,导致读写顺序错乱,影响了共享内存的值。
那么,如果线程只读操作共享内存,就不存在并发问题了。
1. 如何实现不变性
第一,需要类本身是final的,防止子类继承他而修改不变性。
第二,需要类的属性都是final的,并且只存在读方法。
第三,使用深拷贝修改对象,简单说就是在修改对象时,new一个新的对象返回。但是一方面考虑麻烦,另一方面clone方法是native方法,效率更高,一般我们还是选择使用clone方法实现。
Java中有一个很好的例子,就是String类。请移步-> https://blog.csdn.net/sougou_1323/article/details/103223855
2. 享元模式
简单说,享元模式就是创建缓存,Java中的包装类除了Float和Double都有缓存。请移步 2. 3)-> https://blog.csdn.net/sougou_1323/article/details/103227907
这里需要注意一点,请不要使用包装类型作为锁,如下面的栗子,al和bl看上去是两个锁,实际上是一个锁,这样很容易出问题。
class A {
Integer al = Integer.valueOf(1);
public void setA() {
synchronized (al) {
// ...
}
}
}
class B {
Integer bl = Integer.valueOf(1);
public void setB() {
synchronized (bl) {
// ...
}
}
}
3. 假不变性
假?请看下面的栗子。这里的问题本质是,Java中都是引用,user引用是final的,但是User对象不是final的。换句话说,user的引用确实不可变,但是user实例的内容是可变的。所以在使用不变性模式的时候一定要确认是否要求属性对象也具备不可变性。
public final class Shop{
private final User user;
public Account(User user){
this.user = user;
}
public User getUser(){
return this.user;
}
}
public class User {
private String name;
private Integer age;
public void addAge() {
age += 1;
}
}
还有一种方法,将getUser方法配合不变性,不将Shop的属性user返回,而是将User复制一份返回。这样不管外界拿到user怎么折腾都不会影响到Shop的属性user,也就保证了不变性,但是这种实现方式比较消耗内存,可能会引起频繁的GC,具体还要看实际情况来决定。