Immuteable Object - 不可变对象
ShawJie

Immuteable Object

不可变对象模式,多线程共享变量的情况下,既能保证共享变量访问的线程安全,又能避免锁本身带来的消耗所产生的模式。

问题起源

在项目开发过程中,涉及多线程部分的功能多少都会碰到多线程间共享变量的问题,若还存在多个线程都可能对共享变量进行修改的可能性,为保证访问数据的一致性,通常会使用同步访问控制,如显示锁和CAS操作。而锁操作会带来额外的开销,如上下文切换,等待时间等。

模式描述

Immuteable Object(不可变对象)意图是通过使用对外可见但不可变对象使被共享对象具有无需加锁的线程安全性。Java种的String、Integer对象即是如此。

模式架构

image

  • Immuteable Object:负责存储一组不可变数据,该参与者不对外暴露可以修改内容数据的方法。
    • getStateX, getStateY: 这些方法返回Immuteable Object实例所维护的数据的值,这些变量在对象实例化时通过构造器参数获得值。
    • getStateSnapshot:返回Immuteable Object实例所维护的一组数据的快照
  • Manipulator:负责维护Immuteable Object对象的状态变更,当相应的实体状态改变时,参与者负责生成新的Immutable Object以反映新的实例状态。
    • changeStateTo:根据新的状态值生成Immuteable Object实例

模式运行时序图

image


客户端通过Imuuteable Object提供的get方法获取模式内部的数据值 -> 当客户端需要更新数据值时,调用ManipulatorchangeStateTo方法更新数据 -> Manipulator通过创建新的Immuteable Object反映数据的更新,并返回刚创建的不可变对象 -> 客户端调用Immuteable Object对象的getStateSnapshot方法获取实例数据快照


  1. 通过final字段修饰类对象,以及类所持字段,防止其状态被通过获取到的对象直接修改。
    Ps: 通过final字段修饰的对象可以保证其修饰对象或字段是已完成初始化的
  2. 引用了其它可变对象的字段,需要将字段修饰符设为private,且值不能对外暴露,若有方法需要这些字段值,则应该进行防御性复制。

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Immutable Object
public final class MuteClass{
private static volatile MuteClass instance = new MuteClass();
private final List<String> field;

public MuteClass(String field1, String field2, String field3){
/* 从数据库加载数据并写入内存 */
this.field = ...
}



public static MuteClass getInstance(){
return instance;
}

public String getFieldData(int index){
return field.get(index);
}

public static void setInstance(MuteClass newInstance){
instance = newInstance;
}

private static List<String> deepCopy(List<String> field){
List<String> cloneObj = new List<>();
for(String item : field){
cloneObj.add(item);
}
return cloneObj;
}

public List<String> getFieldList(){
return collections.unmodifiableCollection(deepCopy(field));
}
}

总结

由于不可变对象天然的线程安全性,使得开发过程中可以避免显示锁的使用和消耗

  • 适用
    • 描述对象状态数据变化不频繁
    • 对一组数据进行修改且要保证数据原子性
    • 作为HashMap的Key
  • 注意
    • 需对可变字段做防御性复制