併發應用中不可變資料結構

banq發表於2010-02-02
併發並行程式設計是當前熱點,過去我們知道使用鎖synchronization來解決多執行緒併發訪問同一個資料結構時共享問題,甚至我們懷疑資料共享方式本身是不是就錯了?所以,雲端計算的資料喂任務模式開始盛行,但是資料共享方式從我們開始軟體第一天就已經習慣,如何在這個共享模式下實現高併發訪問呢?也就是不使用鎖synchronization,那麼就透過不變性Immutable模式來實現。

Immutable Data Structures in Concurrent Java Applications提出了實現集合物件邊讀邊修改的併發實現方式。

首先指出volatile 的不足,因為不能保證操作volatile 欄位方法的原子性,這樣,還是需要鎖synchronization來修飾其操作方法,該文提出使用final來替代volatile,如果需要修改final的欄位值,就用這個物件來替換,這個概念符合DDD中值物件定義,值物件是不可變的,一旦變化,整個物件更換,同時也符合併發模型,如下類:


public final class Contact {
    private final String name;
    private final String email;
    private final phone;

    public Contact(String name, String email, String phone) {
        this.name = name;
        this.email = email;
        this.phone = phone;
    }

    public String getName() {return name;}
    public String getEmail() {return email;}
    public String getPhone() {return phone;}
}
<p class="indent">


如果我們有一個Contact物件的集合:聯絡人名單集合,然後給這個名單中每個聯絡人傳送Email:
public void sendMessages(Map contactMap) {
sendEmail(contactMap.values());
}
contactMap是Contact集合,contactMap.values是遍歷contactMap中元素Contact物件。如果在遍歷發生Email同時,有新的Contact物件加入到contactMap集合中,這時會丟擲併發錯誤。當然,可以使用ConcurrentMap來實現Map。

但是該文提出一個不可變Map也許能獲得更好地併發效能。

public class ImmutableMap implements Map {
    private final Map map;

    public ImmutableMap() {
        this(Collections.EMPTY_MAP);
    }

    public ImmutableMap immutablePut(K key, V value) {
        Map newMap = new HashMap(map);
        newMap.put(key, value);
        return new ImmutableMap(newMap);//創新新的不可變Map
    }

    public ImmutableMap immutableRemove(K key) {
        Map newMap = new HashMap(map);
        newMap.remove(key);
        return new ImmutableMap(newMap);
    }

    private ImmutableMap(Map delegate) {
        this.map = Collections.unmodifiableMap(delegate);
    }

    // All the map methods are simply delegated to the map field.
    // To conserve space they are not shown here.
}
<p class="indent">

該Map的特點就是遵循值物件模型的特點,集合Map作為一個值物件模型,一旦其元素髮生變化,如新增或刪除元素,返回一個新的集合Map物件。

獲得使用該不可變Map的程式碼如下:

public class ContactService {
    private final ReentrantLock lock = new ReentrantLock();
    //注意 volatile 
    private volatile ImmutableMap contacts = new ImmutableMap();

    public void addContact(Contact contact) {
        lock.lock();//使用鎖來實現變動
        try {
            contacts = contacts.immutablePut(contact.getName(), contact);
        } finally {
            lock.unlock();
        }
    }

    public ImmutableMap getContacts() {
        return contacts;
    }
}
<p class="indent">


這樣,透過避免使用鎖synchronization,而是透過業務設計出值物件,然後使用不可變模式來獲得更好地效能,從這裡也可以看出物件導向設計並不會影響效能,反而能提升效能。

相關文章