多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁。而锁本身又会带来一些问题和开销。Immutable 模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线程安全,又能避免引入锁可能带来的问题和开销。
Immutable 模式的意图是通过使用对外可见的状态不可变的对象,即对象一经创建,其对外可见的状态就保持不变,例如Java中的String和Integer。Immutable Object 共享对象“天生”具有线程安全性,而无须额外地同步访问控制。从而既保证了数据一致性,又避免了同步访问控制所产生的额外开销和问题,也简化了编程。
1.可变对象
这个类是可变的。且是非线程安全的。 当并发调用setXY(double x, double y)
会导致代码交替执行,状态被覆写。
public class Point {
private double x;
private double y;
public Location(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setXY(double x, double y) {
this.x = x;
this.y = y;
}
}
2.不可变对象
Immutable 模式将现实世界中状态可变的实体建模为状态不可变对象,并通过创建不同的状态不可变的对象来反映实现世界实体的状态变更。
创建不可变对象的要点。
- 类由final修饰,防止子类继承之后破坏不可变的机制。
- 字段由final修饰,一则在语义层面表示不可变,二则在JMM保证了对象的初始化安全,防止线程使用未初始化字段。
- 在构造对象时,没有泄露this。
- 如果内部有可变字段,必须保证这些字段为private。且如果需要在某些方法上返回这些对象,需要考虑防御性复制。
public final class Location {
public final double x;
public final double y;
public Location(double x, double y) {
this.x = x;
this.y = y;
}
}
3. 适用场景
- 采用Immutable 模式,将这一组相关的数据“组合”成一个不可变对象,则对这一组数据的操作就可以无须加显式锁也能保证原子性。
- 不可变对象非常适于用作HashMap的Key,由于不可变对象的状态不变,因此其Hash Code也不变。
- 被建模对象的状态变化不频繁。否则频繁创建新的不可变对象,会增加JVM垃圾回收(Garbage Collection)的负担和CPU消耗。
总结
有一种对象,自它生下来就不增不减、不垢不净、佛教称之为舍利子。和Immutable 模式很类似。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。