Лучший подход к использованию в Java 6 для одновременного доступа к списку - PullRequest
7 голосов
/ 16 октября 2008

У меня есть объект List, доступ к которому осуществляется несколькими потоками. В основном это один поток, а в некоторых случаях два потока, которые обновляют список. Из этого списка можно прочитать от одного до пяти потоков, в зависимости от количества обрабатываемых пользовательских запросов. Список - это не очередь задач, которые нужно выполнить, это список объектов домена, которые извлекаются и обновляются одновременно.

Теперь есть несколько способов сделать доступ к этому списку потокобезопасным:
-используемый синхронизированный блок
-пользовательский режим Блокировка (т. е. операции чтения и записи имеют одинаковую блокировку)
-use ReadWriteLock
-использовать один из новых ConcurrentBLABLBA классов коллекции

Мой вопрос:
Какой оптимальный подход для использования, учитывая, что cricital секции обычно не содержат много операций (в основном, просто добавление / удаление / вставка или получение элементов из списка)?
Можете ли вы порекомендовать другой подход, не указанный выше?

Некоторые ограничения
-оптимальная производительность имеет решающее значение, использование памяти не так много
-это должен быть упорядоченный список (в настоящее время синхронизируется с ArrayList ), хотя не отсортированный список (т.е. не отсортированный с использованием Comparable или Comparator, но в соответствии с порядком вставки)
- список будет большим, содержит до 100000 доменных объектов, поэтому использование чего-то вроде CopyOnWriteArrayList неосуществимо
- запись / обновление контуров обычно выполняется очень быстро, просто добавляя / удаляя / вставляя или заменяя (устанавливая)
- операции чтения будут в основном выполнять вызов elementAt (index) большую часть времени, хотя некоторые операции чтения могут выполнять бинарный поиск или indexOf (element)
- нет прямой итерации по списку, хотя такая операция, как indexOf (..), будет проходить по списку

Ответы [ 5 ]

3 голосов
/ 16 октября 2008

Нужно ли использовать последовательный список? Если структура типа карты является более подходящей, вы можете использовать ConcurrentHashMap. Со списком, ReadWriteLock, вероятно, самый эффективный способ.

Изменить, чтобы отразить редактирование OP: Бинарный поиск в порядке вставки? Вы храните временную метку и используете ее для сравнения в своем бинарном поиске? Если это так, вы можете использовать метку времени в качестве ключа и ConcurrentSkipListMap в качестве контейнера (который поддерживает порядок ключей).

1 голос
/ 23 марта 2009

Вы можете использовать оболочку, которая реализует синхронизацию:

import java.util.Collections;
import java.util.ArrayList;

ArrayList list = new ArrayList();
List syncList = Collections.synchronizedList(list);

// make sure you only use syncList for your future calls... 

Это простое решение. Я бы попробовал это, прежде чем прибегать к более сложным решениям.

1 голос
/ 16 октября 2008

I секунда Предложение Telcontar базы данных, поскольку они фактически предназначены для управления таким масштабом данных и согласования между потоками, в то время как коллекции в памяти - нет.

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

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

Да, это довольно сложное решение. Составляющие этого:

  • Протокол для загрузки диапазона данных, скажем, пунктов с 478712 по 478901, а не целого
  • Протокол для получения обновлений об измененных данных
  • Класс кэша, который хранит элементы по их известному индексу на сервере
  • Поток, принадлежащий тому кешу, который общался с сервером. Это единственный поток, который пишет в саму коллекцию
  • Поток, принадлежащий этому кешу, который обрабатывает обратные вызовы при получении данных
  • Интерфейс, который реализуют компоненты пользовательского интерфейса, чтобы позволить им получать данные, когда они были загружены

При первом ударе кости этого кэша могут выглядеть примерно так:

class ServerCacheViewThingy {
    private static final int ACCEPTABLE_SIZE = 500;
    private int viewStart, viewLength;
    final Map<Integer, Record> items
            = new HashMap<Integer, Record>(1000);
    final ConcurrentLinkedQueue<Callback> callbackQueue
            = new ConcurrentLinkedQueue<Callback>();

    public void getRecords (int start, int length, ViewReciever reciever) {
        // remember the current view, to prevent records within
        // this view from being accidentally pruned.
        viewStart = start;
        viewLenght = length;

        // if the selected area is not already loaded, send a request
        // to load that area
        if (!rangeLoaded(start, length))
            addLoadRequest(start, length);

        // add the reciever to the queue, so it will be processed
        // when the data has arrived
        if (reciever != null)
            callbackQueue.add(new Callback(start, length, reciever));
    }

    class Callback {
        int start;
        int length;
        ViewReciever reciever;
        ...
    }

    class EditorThread extends Thread {

        private void prune () {
            if (items.size() <= ACCEPTABLE_SIZE)
                return;
            for (Map.Entry<Integer, Record> entry : items.entrySet()) {
                int position = entry.key();
                // if the position is outside the current view,
                // remove that item from the cache
                ...
            }
        }

        private void markDirty (int from) { ... }

        ....
    }

    class CallbackThread extends Thread {
        public void notifyCallback (Callback callback);
        private void processCallback (Callback) {
            readRecords
        }
    }
}

interface ViewReciever {
    void recieveData (int viewStart, Record[] records);
    void recieveTimeout ();
}

Очевидно, вам придется заполнить множество деталей для себя.

1 голос
/ 16 октября 2008

Я не знаю, является ли это возможным решением проблемы, но ... для меня имеет смысл использовать менеджер баз данных для хранения такого огромного количества данных и позволить ему управлять транзакциями

1 голос
/ 16 октября 2008

Что делают темы чтения? Если они выполняют итерации по списку, то вам действительно нужно убедиться, что никто не трогает список в течение всего процесса итерации, иначе вы можете получить очень странные результаты.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...