假设我有两个 ArrayList。
A1 = { 1, 2, 3}
A2 = { 4, 5}
我想将元素“4”移动到 A1。我需要一个线程安全的解决方案来做到这一点。例如,以下步骤可能会在给定时间丢失元素“4”。
- 从 A2 中删除元素 4
此时,如果另一个线程遍历这两个数组列表的元素,它将看不到元素 4。
- 将元素 4 插入到 A1
我希望这是一个完整的过程。
最佳答案
您绝对应该阅读 thread synchronization .
主要思想:每次对共享状态(=两个列表)的访问都必须同步。在您的情况下,可以通过使用 synchronized
关键字轻松完成此操作。
示例:
public class ThreadSafeObject<T> {
private final List<T> a1 = new ArrayList<>();
private final List<T> a2 = new ArrayList<>();
public synchronized void moveFromA1ToA2(int index) {
T elem = a1.remove(index);
a2.add(elem);
}
public synchronized void traverseA1(Consumer<? super T> consumer) {
a1.forEach(consumer);
}
}
正如您所看到的,这两个方法都是同步的
,这意味着如果另一个线程已经在执行这两个方法中的任何一个,则线程不得进入这两个方法中的任何一个。
请注意,仅同步移动方法不够。并且您不得允许直接访问这两个列表。
有关 synchronized
关键字和内在锁的更多详细信息,请阅读 the documentation mentioned above .
添加
由于对此答案的讨论,我决定使用 ReadWriteLock
添加另一个解决方案。此解决方案的优点是线程在遍历列表(= 读访问)时不会互相锁定。
请注意,主要原则是相同的:我们必须使每次对列表的访问都是线程安全的!更重要的是,因为我们有一个 invariant包括两个列表,无论我们想访问第一个、第二个还是两个列表,我们都必须使用同一个锁。
尽管如此,只有准确的测量才能表明该解决方案在您的具体情况下是否确实更快(使用锁可能比同步
更昂贵)。
public class ThreadSafeObject<T> {
private final List<T> a1 = new ArrayList<>();
private final List<T> a2 = new ArrayList<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock(false);
public void moveFromA1ToA2(int index) {
lock.writeLock().lock();
try {
T elem = a1.remove(index);
a2.add(elem);
} finally {
lock.writeLock().unlock();
}
}
public void traverseA1(Consumer<? super T> consumer) {
lock.readLock().lock();
try {
a1.forEach(consumer);
} finally {
lock.readLock().unlock();
}
}
}
注意:任何进行更复杂、更巧妙的同步的尝试都可能会失败。锁 strip 化可能是一个可能的改进,但因此我们必须了解有关您的要求的更多详细信息(例如,元素的顺序重要吗?我们可以在中间有 null
项吗?您真的想遍历吗?按特定顺序列出列表,或者仅搜索项目就足够了,等等)。可能不值得付出努力。
关于java - 线程安全地在两个数组列表之间移动数组列表元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52293062/