具有原子替换的线程安全可序列化集合
Thread-safe serializable Collection with atomic replace
当多个线程通过 RMI 访问同一个服务器时,我的程序遇到了问题。服务器包含一个列表作为缓存,并执行一些昂贵的计算,有时会更改该列表。计算完成后,列表将被序列化并发送给客户端。
第一个问题: 如果列表在序列化时被更改(例如,由不同的客户端请求一些数据)(可能)抛出 ConcurrentModificationException
,导致EOFException
用于客户端的 RMI 调用/反序列化。
因此我需要某种列表结构,它对于序列化是“稳定的”,同时可能会被不同的线程更改。
我们尝试过的解决方案:
- 常规 ArrayList / Set - 由于并发而无法正常工作
- 在每次序列化之前深度复制整个结构 - 太昂贵了
CopyOnWriteArrayList
- 也很昂贵,因为它复制列表 and
揭示了第二个问题:我们需要能够自动替换列表中当前非线程安全的任何元素(先删除,然后添加(偶数更昂贵))或只能通过锁定列表来实现,因此只能按顺序执行不同的线程。
因此我的问题是:
Do you know of a Collection
implementation which allows us to serialize
the Collection thread-safe while other Threads modify it and
which contains some way of atomically replacing
elements?
A bonus would be if the list would not
need to be copied
before serialization! Creating a snapshot for every serialization would be okay, but still 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
最简单的解决方案是隐含到 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();
}
}
提供您喜欢的任何操作,只需正确使用读锁或写锁即可。
我最初的错误想法是 CopyOnWriteArrayList
是个坏主意,因为它会复制所有内容。但是当然它只执行浅拷贝,只复制引用,而不是复制所有对象的深拷贝。
因此我们显然选择了 CopyOnWriteArrayList
,因为它已经提供了很多需要的功能。唯一剩下的问题是 replace
,它甚至变得更复杂成为 addIfAbsentOrReplace
。
我们尝试了 CopyOnWriteArraySet
,但这不符合我们的需要,因为它只提供 addIfAbsent
。但在我们的例子中,我们有一个名为 c1
的 class C
实例,我们需要存储它,然后用更新的新实例 c2
替换它。当然我们覆盖equals
和hashCode
。现在我们必须选择我们是否希望两个仅有最小差异的对象等于 return 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;
}
}
当多个线程通过 RMI 访问同一个服务器时,我的程序遇到了问题。服务器包含一个列表作为缓存,并执行一些昂贵的计算,有时会更改该列表。计算完成后,列表将被序列化并发送给客户端。
第一个问题: 如果列表在序列化时被更改(例如,由不同的客户端请求一些数据)(可能)抛出 ConcurrentModificationException
,导致EOFException
用于客户端的 RMI 调用/反序列化。
因此我需要某种列表结构,它对于序列化是“稳定的”,同时可能会被不同的线程更改。
我们尝试过的解决方案:
- 常规 ArrayList / Set - 由于并发而无法正常工作
- 在每次序列化之前深度复制整个结构 - 太昂贵了
CopyOnWriteArrayList
- 也很昂贵,因为它复制列表 and
揭示了第二个问题:我们需要能够自动替换列表中当前非线程安全的任何元素(先删除,然后添加(偶数更昂贵))或只能通过锁定列表来实现,因此只能按顺序执行不同的线程。
因此我的问题是:
Do you know of a
Collection
implementation which allows us toserialize
the Collection thread-safe while other Threads modify itand
which contains some way ofatomically replacing
elements?A bonus would be if the list would
not
need to becopied
before serialization! Creating a snapshot for every serialization would be okay, but still 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
最简单的解决方案是隐含到 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();
}
}
提供您喜欢的任何操作,只需正确使用读锁或写锁即可。
我最初的错误想法是 CopyOnWriteArrayList
是个坏主意,因为它会复制所有内容。但是当然它只执行浅拷贝,只复制引用,而不是复制所有对象的深拷贝。
因此我们显然选择了 CopyOnWriteArrayList
,因为它已经提供了很多需要的功能。唯一剩下的问题是 replace
,它甚至变得更复杂成为 addIfAbsentOrReplace
。
我们尝试了 CopyOnWriteArraySet
,但这不符合我们的需要,因为它只提供 addIfAbsent
。但在我们的例子中,我们有一个名为 c1
的 class C
实例,我们需要存储它,然后用更新的新实例 c2
替换它。当然我们覆盖equals
和hashCode
。现在我们必须选择我们是否希望两个仅有最小差异的对象等于 return 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;
}
}