并发应用中不可变数据结构

10-02-02 banq
并发并行编程是当前热点,过去我们知道使用锁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,而是通过业务设计出值对象,然后使用不可变模式来获得更好地性能,从这里也可以看出面向对象设计并不会影响性能,反而能提升性能。

chabulier
2010-02-03 16:27
怎么感觉有点像是CVS的感觉? 这样每次都产生一个object,在数据量大的时候综合性能是提升了还是降低了?

可能一些简单是小数据量, 高并发的地方还是很有用的。

banq
2010-02-03 16:59
2010年02月03日 16:27 "chabulier"的内容
可能一些简单是小数据量


是这样,适合小数据量对象,而值对象一般恰恰都是小数据量对象。

javawebkaifa
2012-04-19 15:14
那所谓的不变性就是:对你所需的数据进行细分,然后才有,没一次重新刷新一次数据,但是当你在创建一个新对象的时候,别人需要访问了,那还不是只有等上一个新对象创建浩后才可以进行访问?是吗?