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

并发并行编程是当前热点,过去我们知道使用锁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;}
}

如果我们有一个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.
}

该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;
}
}

这样,通过避免使用锁synchronization,而是通过业务设计出值对象,然后使用不可变模式来获得更好地性能,从这里也可以看出面向对象设计并不会影响性能,反而能提升性能。

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

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

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

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

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