【问题标题】:Thread-safe serializable Collection with atomic replace具有原子替换的线程安全可序列化集合
【发布时间】:2015-09-28 14:55:39
【问题描述】:

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

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

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

我们尝试过的解决方案

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

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

因此我的问题是:

您知道Collection 实现,它允许我们serialize 集合线程安全,而其他线程修改它and,其中包含某种方式的atomically replacing 元素?

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

问题说明(C=计算,A=添加到列表,R=从列表中删除,S=序列化)

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

【问题讨论】:

  • 删除后添加时,替换是否必须在列表末尾?
  • @PaulBoddington 不,顺序无关紧要,我们也可以使用 Set。
  • 删除是什么意思?你是删除已知索引的元素(如remove(index))还是使用搜索和删除(如remove(Object))?
  • @TagirValeev replaceWithObj(Object, Object),不是通过索引,而是通过检查每个对象是否相等。目前通过调用remove(Object) 然后add(slightlyDifferentObj)

标签: java multithreading serialization collections copyonwritearraylist


【解决方案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();
    }
}

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

【讨论】:

  • 谢谢你的回答,我明天上班试试。使用两个单独的锁进行读取和写入看起来很有希望。我将不得不检查这是否满足我的所有需求!
  • 但是鉴于 cmets 中显示的信息(“我们也可以使用 Set”),非常推荐将 ArrayList 替换为 HashSet,因为它可以摆脱线性搜索在replace 方法中。
【解决方案2】:

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

因此,我们显然选择了CopyOnWriteArrayList,因为它已经提供了许多所需的功能。唯一剩下的问题是replace,它甚至变得更加复杂,成为addIfAbsentOrReplace

我们尝试了CopyOnWriteArraySet,但这不符合我们的需求,因为它只提供addIfAbsent。但是在我们的例子中,我们有一个名为c1 的类C 的实例,我们需要存储它,然后用更新的新实例c2 替换它。当然我们会覆盖equalshashCode。现在我们必须选择是否希望相等性返回 truefalse 以用于这两个仅有最小差异的对象。这两个选项都不起作用,因为

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    • 2011-12-15
    • 2013-07-06
    • 1970-01-01
    • 2019-02-10
    • 2012-05-27
    • 1970-01-01
    相关资源
    最近更新 更多