2016-02-08 3 views
1

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

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

Итак, я написал следующий код и, к сожалению, бросает исключение ConcurrentModificationException. .

import java.util.*; 

public class Test { 
public static void main(String[] args) { 
    // assume c1 is private, nicely encapsulated in some class 
    final Collection col1 = Collections.synchronizedCollection(new ArrayList()); 
    // this unmodifiable version is public 
    final Collection unmodcol1 = Collections.unmodifiableCollection(col1); 

    col1.add("a"); 
    col1.add("b"); 

    new Thread(new Runnable() { 
     public void run() { 
      while (true) { 
       // no way to synchronize on c1! 
       for (Iterator it = unmodcol1 .iterator(); it.hasNext(); it.next()) 
        ; 
      } 
     } 
    }).start(); 

    while (true) { 
     col1 .add("c"); 
     col1 .remove("c"); 
    } 
    } 
} 

Так что мой вопрос Как синхронизировать нередактируемую коллекцию?

Чтобы добавить

Когда клиент, который получил коллекцию хочет перебрать его элементы

1) не обязательно знать, что это синхронизируется сбор и

2) даже если это так, он не может правильно синхронизировать мьютекс обертки синхронизации, чтобы перебирать его элементы. Штраф, как описано в Collections.synchronizedCollection, является недетерминированным поведением.

Из моего понимания Ввод немодифицируемой обертки в синхронизированную коллекцию не оставляет доступа к мьютексу, который должен удерживаться для правильной итерации.

+3

Посмотрите, можете ли вы просто вернуть копию/моментальный снимок. делает вещи намного проще – ZhongYu

+0

@ShowStopper, я не понимаю, что вы добавили в вопрос. 2. клиенту не нужно синхронизировать, JVM и Iterator делают это за него. – Gavriel

+0

Collections.java:2030 общественный Итератор итератор() { return c.iterator(); // Должен быть вручную синхронизирован пользователем! }. Как вы можете видеть, ваша попытка не предотвратит CME, поскольку итератор не синхронизирован. Если вы хотите, чтобы итератор был синхронизирован, вам, возможно, придется обернуть его самостоятельно. Тем не менее, этот итератор будет недетерминированным, поскольку добавление элемента перед следующим() означает, что клиент не коснется его. –

ответ

0

Вы спрашиваете «Как синхронизировать немодифицируемые коллекции», но на самом деле это не то, что вы сделали в своем коде. Вы сделали сборку синхронизированной коллекции неизменяемой. Если вы сделаете свою коллекцию неизменяемой, а затем синхронизируйте ее, тогда вы получите то, что хотите.

// you'll need to create the list 
final ArrayList list = new ArrayList(); 
// and add items to it while it's still modifiable: 
list.add("a"); 
list.add("b"); 
final Collection unmodcol1 = Collections.unmodifiableCollection(list); 

final Collection col1 = Collections.synchronizedCollection(unmodcol1); 

Однако, добавление, удаление внутри while все равно будет работать по той же причине.

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

+0

Нет ..... пожалуйста, запустите код перед ответом. Я получаю следующее исключение. Теперь. Исключение в потоке "основного" java.lang.UnsupportedOperationException \t в java.util.Collections $ UnmodifiableCollection.add (Collections.java:1055) \t в java.util.Collections $ SynchronizedCollection.add (Collections.java:2035) \t на Test.main (Test.java:10) –

+1

Точно. И почему так? Потому что вы пытаетесь изменить немодифицируемый список, добавив к нему – Gavriel

+0

Вот почему я сначала синхронизировал. –

1

Если вы можете гарантировать, что только для чтения клиентов коллекции synchronize на коллекции, синхронизации на этом же окне вашего производителя:

/* In the producer... */ 
Collection<Object> collection = new ArrayList<>(); 
Collection<Object> tmp = Collections.unmodifiableCollection(collection); 
Collection<Object> view = Collections.synchronizedCollection(tmp); 
synchronized (view) { 
    collection.add("a"); 
    collection.add("b"); 
} 
/* Give clients access only to "view" ... */ 

/* Meanwhile, in the client: */ 
synchronized (view) { 
    for (Object o : view) { 
    /* Do something with o */ 
    } 
} 
1

Вы должны решить, на несколько вещей, в первую очередь.

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

B. Или им нужно снова позвонить, чтобы получить новую коллекцию?

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

Возможные решения:

  1. Возвращение копии, не нужно обернуть его в нередактируемым даже. Просто заблокируйте его, создавая его. synchronized (collection) { return new ArrayList(collection); } Не требуется дополнительной синхронизации. Пример реализации варианта B выше.
  2. Как 1, но автоматически по самой структуре данных, используйте CopyOnWriteArrayList и верните его (завернутый в немодифицируемый). Примечание. Это означает, что запись в коллекцию стоит дорого. Читает - нет. С другой стороны, даже итерация на нем является потокобезопасной. Никакой синхронизации не требуется. Поддерживает вариант A выше.
  3. В зависимости от свойств структуры данных вам нужно перейти на список non RandomAccess, например ConcurrentLinkedQueue или ConcurrentLinkedDeque, который позволяет выполнять итерацию и т. Д. По структуре данных без дополнительной синхронизации. Опять же, завернутый в немодифицируемый. Поддерживает вариант A выше.

Я бы выбрал вариант B-1 для общего случая и для начала работы. Но это зависит как обычно.

0

Возможно, вы захотите использовать одну из параллельных коллекций. Это даст вам то, что вам нужно, если я правильно прочитаю ваш вопрос. Просто примите оплату по стоимости.

List<T> myCollection = new CopyOnWriteArrayList<T>(); 
List<T> clientsCollection = Collections.unmodifiableList(myCollection); 

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