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 ();
}
Очевидно, вам придется заполнить множество деталей для себя.