假设我有两个ArrayLists。
A1 = { 1, 2, 3}
A2 = { 4, 5}
我想将元素“ 4”移动到A1。我需要一个线程安全的解决方案。例如,以下步骤可能会在给定的时间丢失元素“ 4”。
这时,如果另一个线程遍历这两个数组列表的元素,它将看不到元素4。
我希望这是一个完整的过程。
答案 0 :(得分:1)
您绝对应该阅读有关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
,这意味着如果另一个线程已经在执行这些方法中的任何一个,则线程不得输入这两个方法中的任何一个。
请注意,仅同步move方法还不够(strong)。并且您必须不允许直接访问这两个列表。
有关synchronized
关键字和内部锁的更多详细信息,请阅读the documentation mentioned above。
添加
由于对此答案的讨论,我决定使用ReadWriteLock
添加另一种解决方案。这种解决方案的优势在于,线程在遍历列表时不会互相锁定(=读取访问权限)。
请注意,主要原理是相同的:我们必须使对列表线程的每个访问安全!甚至更多,因为我们有一个invariant包含两个列表,所以无论我们要访问第一个列表,第二个列表还是两个列表,我们都必须使用相同的锁。
尽管如此,只有精确的测量才能显示出该解决方案在您的具体情况下是否真的更快(使用锁可能比synchronized
昂贵)。
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();
}
}
}
注意:任何进行更复杂,更聪明的同步的尝试都可能会失败。锁定条带化可能是一种可能的改进,但是因此我们必须了解有关您的要求的更多详细信息(例如,元素的顺序是否重要,我们之间是否可以有null
个项目,您是否真的想遍历其中的列表?确定顺序还是仅搜索项目等就足够了。)可能不值得付出努力。