Безопасное перемещение элементов массива среди двух массивов - PullRequest
0 голосов
/ 12 сентября 2018

Допустим, у меня есть два ArrayList.

A1 = { 1, 2, 3}
A2 = { 4, 5}

Я хочу переместить элемент '4' в A1.Мне нужно поточно-безопасное решение, чтобы сделать это.Например, приведенные ниже шаги могут потерять элемент '4' в данный момент времени.

  1. Удалить элемент 4 из A2

На этом этапе, если другой потокобходит элементы этих двух массивов, он не видит элемент 4.

Вставить элемент 4 в A1

Я хочу, чтобы это был целый процесс.

1 Ответ

0 голосов
/ 12 сентября 2018

Вы обязательно должны прочитать о синхронизации потоков .

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

...