Вы обязательно должны прочитать о синхронизации потоков .
Основная идея: Каждый доступ к общему состоянию (= два списка) должен быть синхронизирован. В вашем случае это легко сделать с помощью ключевого слова 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
, что означает, что поток не должен вводить ни один из двух методов, если другой поток уже выполняет любой из этих методов.
Обратите внимание, что недостаточно , чтобы просто синхронизировать метод перемещения. И вы не должны разрешать прямой доступ к двум спискам.
Подробнее о ключевом слове synchronized
и внутренних замках читайте в документации, упомянутой выше .
Добавление
В связи с обсуждением этого ответа я решил добавить другое решение, используя ReadWriteLock
. Преимущество этого решения заключается в том, что потоки не блокируют друг друга при простом обходе списка (= доступ для чтения).
Обратите внимание, что основной принцип тот же: мы должны сделать каждый доступ к спискам безопасным! Более того, поскольку у нас есть инвариант , который включает оба списка, мы должны использовать одну и ту же блокировку независимо от того, хотим ли мы получить доступ к первому, второму или обоим спискам.
Тем не менее, только точное измерение может показать, действительно ли это решение действительно быстрее в вашей конкретной ситуации (использование замков может стоить дороже, чем 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
элементов между ними, вы действительно хотите просмотреть списки в определенном порядке? заказать или достаточно просто искать предметы и т. д.). Не стоило бы усилий.