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

相關文章
相關標籤/搜索