Java多线程编程之不可变对象模式

       在多线程环境中,为了保证共享数据的一致性,每每须要对共享数据的使用进行加锁,可是加锁操做自己就会带来必定的开销,这里可使用将共享数据使用不可变对象进行封装,从而避免加锁操做。java

1. 模型角色

       不可变对象指的是,对象内部没有提供任何可供修改对象数据的方法,若是须要修改共享变量的任何数据,都须要先构建整个共享对象,而后对共享对象进行总体的替换,经过这种方式来达到对共享对象数据一致性的保证。以下是不可变对象设计的类图:git

不可变对象类图

以下是各个角色功能的描述:多线程

  • ImmutableObject:不可变对象的载体。对于须要一致性更改的数据,都须要放入不可变对象中,对于不可变对象,须要注意以下几点:
    • 不可变对象的属性必须使用final修饰,以防止属性被意外修改,而且final还能够保证JVM在该对象构造完成时该属性已经初始化成功(JVM在构造完对象时可能只是为其分配了引用空间,而各个属性值可能还未初始化完成);
    • 属性的设值必须在构造方法中统一构造完成,其他的方法只是提供的查询各个属性相关的方法;
    • 对于可变状态的引用类型属性,如集合,在获取该类型的属性时,必须返回该属性的一个深度复制结果,以防止不可变对象的该属性值被客户端修改;
    • 不可变对象的类必须使用final修饰,以防止子类对其自己或其方法进行修改;
  • Manipulator:聚合对象的管理类(某些状况可不用)。对于聚合对象的管理,主要有两部分:查询和修改。对于聚合对象的查询,只须要根据必定的规则在Manipulator类中获取该对象便可,对于聚合对象的修改,须要首先经过参数构造一个完整的聚合对象,而后将保存的该聚合对象的引用进行替换便可;
  • Client:获取聚合对象的客户端应用。

2. 使用场景

       对于不可变对象,其主要有以下三种使用场景:this

  • 当某组数据变化不是很频繁,则可使用不可变对象。对于数据的访问,因为不可变对象的引用空间不会发生变化,于是任何线程均可以保有同一个不可变对象的引用,这样能够减小内存的消耗,也能保证数据访问的一致性;
  • 当某组数据须要进行一致性的更新操做时,可使用不可变对象。因为不可变对象可以保证对其任何数据的修改都是对整个对象的替换,于是其可以保证整组数据的一致性。须要注意的是,若是该组数据变动比较频繁,则不宜使用不可变对象,由于这会形成建立大量的不可变对象,从而增长JVM垃圾回收的压力。具体的状况应根据JVM可以使用内存大小与对象更新的频率进行考量;
  • 当须要对象做为Map的键时可使用不可变对象。对于Map而言,其hashCode()方法默认返回的是对象的引用地址,而对于不可变对象而言,因为其引用地址是不会发生变化的,于是即便不对其hashCode()方法进行重写,其也不会发生变化。

3. 使用示例

       对于不可变对象,一个很好的例子就是地址经纬度。笔者所工做的公司处理的业务和房源相关,其中有一部分就是须要处理房源所在点的经纬度信息,这里就可使用不可变对象,由于房源经纬度基本上不会发生变化,而且对其操做也主要是以查询为主,最重要的是,对经纬度的处理必须是经度和纬度同时发生变化,任何状况下只更改了其中一个数据都会产生问题。以下是记录房源经纬度的类:线程

public final class Location {
  private final long id;
  private final String latitude;
  private final String longitude;

  public Location(long id, String latitude, String longitude) {
    this.id = id;
    this.latitude = latitude;
    this.longitude = longitude;
  }

  public long getId() {
    return id;
  }

  public String getLatitude() {
    return latitude;
  }

  public String getLongitude() {
    return longitude;
  }
}

       该Location类也即上述UML类图中的ImmutableObject部分。能够看到,任何对Location对象的修改都必须从新构建一个Location对象。以下是对Location的管理类,用于存储具体的Location信息的:设计

public class LocationHolder {
  
  private final LocationHolder INSTANCE = new LocationHolder();
  private Map<Long, Location> locations;

  private LocationHolder() {
    this.locations = new ConcurrentHashMap<>();
  }
  
  public LocationHolder getInstance() {
    return INSTANCE;
  }

  public Location getLocation(long id) {
    return locations.get(id);
  }

  public void addLocation(long id, String latitude, String longitude) {
    Location location = new Location(id, latitude, longitude);
    locations.put(id, location);
  }
  
  public Map<Long, Location> getLocations() {
    return Collections.unmodifiableMap(locations);
  }
}

        能够看到,这里对Location的管理是经过一个单例类LocationHolder进行的,任何对Location的操做都进行了封装,而且这里批量获取Location,也是返回了一个不可变Map,从而保证原始数据不会做任何修改,若是该Map的键或值任何一方可能发生变化,那么在返回值则必须返回一个深度复制的结果,这样才能保证原始数据的完整性。code

相关文章
相关标签/搜索