JAVA: контроль параллелизма для доступа к списку в Java - PullRequest
5 голосов
/ 15 октября 2010

У меня есть многопоточное приложение, центрированный список которого обновляется (записывается) только основным потоком. Затем у меня есть несколько других потоков, которые должны периодически получать список в его текущем состоянии. Есть ли способ, который может позволить мне сделать это?

Ответы [ 6 ]

12 голосов
/ 15 октября 2010

Это зависит от того, как вы хотите ограничить параллелизм.Самым простым способом, вероятно, является использование CopyOnWriteArrayList.Когда вы извлекаете из него итератор, этот итератор будет отображать то, как список выглядел в тот момент, когда был создан итератор - последующие изменения не будут видны итератору.Плюс в том, что он может справиться с большим количеством споров, недостаток в том, что добавление новых предметов довольно дорого.

Другой способ - это блокировка, возможно, самый простой способ - обернуть список с помощью Collections.synchronizedList и синхронизировать список при итерации.

Третий способ -используя какой-то BlockingQueue и передать новые элементы рабочим.

Редактировать: Поскольку в ОП указано, что требуется только моментальный снимок, CopyOnWriteArrayListвероятно, лучшая альтернатива из коробки.Альтернативой (для более дешевого добавления, но более дорогостоящего чтения) является просто создание копии synchronizedList, когда необходим переход (копирование при чтении, а не копирование при записи):

List<Foo> originalList = Collections.synchronizedList(new ArrayList());

public void mainThread() {
    while(true)
        originalList.add(getSomething());
}

public void workerThread() {
    while(true) {
        List<Foo> copiedList;
        synchronized (originalList) {
             copiedList = originalList.add(something);
        }
        for (Foo f : copiedList) process(f);
    }
}

Редактировать: Если подумать, версия для копирования при чтении может немного упростить, чтобы избежать всех synchronized блоков:

List<Foo> originalList = Collections.synchronizedList(new ArrayList());

public void mainThread() {
    while(true)
        originalList.add(getSomething());
}

public void workerThread() {
    while(true) {
        for (Foo f : originalList.toArray(new Foo[0])) 
            process(f);
    }
}

Редактировать 2: Вот простая оболочка для списка копирования при чтении, в которой не используются никакие помощники, и которая старается быть как можно более мелкой в ​​блокировке (я намеренно сделал ее несколько чрезмерной, граничащей с неоптимальной, чтобы продемонстрироватьгде требуется блокировка):

class CopyOnReadList<T> {

    private final List<T> items = new ArrayList<T>();

    public void add(T item) {
        synchronized (items) {
            // Add item while holding the lock.
            items.add(item);
        }
    }

    public List<T> makeSnapshot() {
        List<T> copy = new ArrayList<T>();
        synchronized (items) {
            // Make a copy while holding the lock.
            for (T t : items) copy.add(t);
        }
        return copy;
    }

}

// Usage:
CopyOnReadList<String> stuff = new CopyOnReadList<String>();
stuff.add("hello");
for (String s : stuff.makeSnapshot())
    System.out.println(s);

Обычно, когда вы блокируете, когда вы:

  1. ... добавляете элемент в список.
  2. ... переберите список, чтобы сделать его копию.
3 голосов
/ 15 октября 2010

Вы можете рассмотреть возможность использования механизма блокировки чтения-записи. Если ваша версия JDK 1.5 или новее, вы можете использовать ReentrantReadWriteLock .

2 голосов
/ 15 октября 2010

Взгляните на Пакет параллелизма Java .Там должно быть что-то, что вы можете использовать.

Нужны ли дочерним потокам доступ только для чтения?Нужно ли просто пункт верхней части списка?Как используемый список может помочь нам лучше понять вашу проблему и указать вам более четкое направление.

Чтобы передать снимок списка, просто создайте новый список, заполненный исходным списком.*

List<Object> newList;
synchronize (originalList)
{
    newList = new ArrayList<Object>(originalList);
}
return newList;

Синхронизированный может быть или не быть полезным здесь.Я не уверен.

1 голос
/ 15 октября 2010

Если темы только «читают» список, с которым вы в безопасности, просто используя List. Если список будет «записан» (каким-либо образом изменен), используйте Vector вместо List.

0 голосов
/ 15 октября 2010

Если запись не частая и размер данных невелик, CopyOnWriteArrayList является отличным выбором.В противном случае ваша производительность записи может быть проблемой.

Если вам не нужен полный интерфейс List (в основном произвольный доступ через индекс), тогда у вас есть больше возможностей.Если ваш вариант использования удовлетворяет интерфейсу очереди, то такие вещи, как ConcurrentLinkedQueue, будут отличным выбором.Если вы можете жить с интерфейсом очереди, то становится возможным нечто подобное:

Queue<Foo> originalList = new ConcurrentLinkedQueue<Foo>();

public void mainWrite() {
    // main thread
    originalList.add(getSomething()); // no locking needed
}

public void workerRead() {
    // executed by worker threads
    // iterate without holding the lock
    for (Foo f: originalList) {
        process(f);
    }
}
0 голосов
/ 15 октября 2010

Если вам просто нужен снимок только для чтения, а список не слишком большой:

private static List<TypeHere> getCurrentList() { 
  /* code to return the list as it currently is */
}
public static List<TypeHere> snapshot() {
  TypeHere[] objects = getCurrentList().toArray(new TypeHere[] {});
  return Collections.unmodifiableList(Arrays.asList(objects));
} 
...