3

当多个线程通过 RMI 访问同一服务器时,我的程序遇到问题。服务器包含一个列表作为缓存并执行一些昂贵的计算,有时会更改该列表。计算完成后,列表将被序列化并发送给客户端。

第一个问题:如果列表在被序列化(例如,由不同的客户端请求某些数据)时发生更改ConcurrentModificationException(可能)抛出一个,导致EOFExceptionRMI 调用/客户端的反序列化。

因此,我需要某种列表结构,它对于序列化是“稳定的”,同时可能被不同的线程更改。

我们尝试过的解决方案

  • 常规 ArrayList / Set - 由于并发而无法正常工作
  • 在每次序列化之前深度复制整个结构 - faaar 太昂贵了
  • CopyOnWriteArrayList- 也很昂贵,因为它复制了列表

揭示第二个问题:我们需要能够以原子方式替换列表中当前不是线程安全的任何元素(首先删除,然后添加(这甚至更昂贵)),或者只能通过锁定列表来实现,因此只做不同的线程按顺序排列。

因此我的问题是:

您是否知道Collection允许我们对serializeCollection 线程安全的实现,而其他线程对其进行修改,and其中包含某种方式的atomically replacing元素?

如果列表not需要copied在序列化之前出现,那将是一个奖励!为每个序列化创建一个快照是可以的,但仍然是 meh :/

问题说明(C=compute,A=add to list,R=remove from list,S=serialize)

Thread1 Thread2
      C 
      A
      A C
      C A
      S C
      S R <---- Remove and add have to be performed without Thread1 serializing 
      S A <---- anything in between (atomically) - and it has to be done without 
      S S       blocking other threads computations and serializations for long
        S       and not third thread must be allowed to start serializing in this
        S       in-between state
        S
4

2 回答 2

1

最简单的解决方案是暗示外部同步到ArrayList,可能通过这样的读写锁:

public class SyncList<T> implements Serializable {
    private static final long serialVersionUID = -6184959782243333803L;

    private List<T> list = new ArrayList<>();
    private transient Lock readLock, writeLock;

    public SyncList() {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readLock = readWriteLock.readLock();
        writeLock = readWriteLock.writeLock();
    }

    public void add(T element) {
        writeLock.lock();
        try {
            list.add(element);
        } finally {
            writeLock.unlock();
        }
    }

    public T get(int index) {
        readLock.lock();
        try {
            return list.get(index);
        } finally {
            readLock.unlock();
        }
    }

    public String dump() {
        readLock.lock();
        try {
            return list.toString();
        } finally {
            readLock.unlock();
        }
    }

    public boolean replace(T old, T newElement) {
        writeLock.lock();
        try {
            int pos = list.indexOf(old);
            if (pos < 0)
                return false;
            list.set(pos, newElement);
            return true;
        } finally {
            writeLock.unlock();
        }
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        readLock.lock();
        try {
            out.writeObject(list);
        } finally {
            readLock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        list = (List<T>) in.readObject();
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readLock = readWriteLock.readLock();
        writeLock = readWriteLock.writeLock();
    }
}

提供您喜欢的任何操作,只要正确使用读锁或写锁即可。

于 2015-09-28T15:56:43.273 回答
0

我最初的错误想法是,这CopyOnWriteArrayList是一个坏主意,因为它复制了所有内容。但当然它只执行浅拷贝,只拷贝引用,而不是拷贝所有对象的深拷贝。

因此,我们显然选择了 ,CopyOnWriteArrayList因为它已经提供了很多所需的功能。唯一剩下的问题replaceaddIfAbsentOrReplace.

我们尝试了,CopyOnWriteArraySet但这不符合我们的需要,因为它只提供addIfAbsent。但是在我们的例子中,我们有一个类的实例Cc1我们需要存储它,然后用更新的新实例替换它c2。当然我们覆盖equalsand hashCode。现在我们必须选择我们是否希望返回相等性,true或者false这两个只有最小不同的对象。两种选择都不起作用,因为

  • true意味着对象是相同的,并且集合甚至不会打扰添加新对象c2,因为c1已经在
  • false将意味着c2将被添加但c1不会被删除

因此CopyOnWriteArrayList。该列表已经提供了一个

public void replaceAll(UnaryOperator<E> operator) { ... }

这有点符合我们的需要。它允许我们通过自定义比较替换我们需要的对象。

我们通过以下方式使用它:

protected <T extends OurSpecialClass> void addIfAbsentOrReplace(T toAdd, List<T> elementList) {
    OurSpecialClassReplaceOperator<T> op = new OurSpecialClassReplaceOperator<>(toAdd);
    synchronized (elementList) {
        elementList.replaceAll(op);
        if (!op.isReplaced()) {
            elementList.add(toAdd);
        }
    }
}

private class OurSpecialClassReplaceOperator<T extends OurSpecialClass> implements UnaryOperator<T> {

    private boolean replaced = false;

    private T toAdd;

    public OurSpecialClassReplaceOperator(T toAdd) {
        this.toAdd = toAdd;
    }

    @Override
    public T apply(T toAdd) {
        if (this.toAdd.getID().equals(toAdd.getID())) {
            replaced = true;
            return this.toAdd;
        }

        return toAdd;
    }

    public boolean isReplaced() {
        return replaced;
    }
}
于 2015-12-26T12:19:21.423 回答