Синхронизация Java на Коллекции с дорогими операциями - PullRequest
2 голосов
/ 26 октября 2011

У меня есть список, который я синхронизирую по имени synchronizedMap в моей функции doMapOperation. В этой функции мне нужно добавлять / удалять элементы с карты и выполнять дорогостоящие операции с этими объектами. Я знаю, что не хочу вызывать дорогостоящую операцию в синхронизированном блоке, но я не знаю, как убедиться, что карта находится в согласованном состоянии, пока я выполняю эти операции. Как правильно это сделать?

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

public void doMapOperation(Object key1, Object key2) {
    synchronized (synchronizedMap) {

        // Remove key1 if it exists.
        if (synchronizedMap.containsKey(key1)) {
            Object value = synchronizedMap.get(key1);
            value.doExpensiveOperation(); // Shouldn't be in synchronized block.

            synchronizedMap.remove(key1);
        }

        // Add key2 if necessary.
        Object value = synchronizedMap.get(key2);
        if (value == null) {
            Object value = new Object();
            synchronizedMap.put(key2, value);
        }

        value.doOtherExpensiveOperation(); // Shouldn't be in synchronized block.
    } // End of synchronization.
}

Я думаю, как продолжение этого вопроса, как бы вы сделали это в цикле?

public void doMapOperation(Object... keys) {
    synchronized (synchronizedMap) {

        // Loop through keys and remove them.
        for (Object key : keys) {
            // Check if map has key, remove if key exists, add if key doesn't.
            if (synchronizedMap.containsKey(key)) {
                Object value = synchronizedMap.get(key);
                value.doExpensiveOperation(); // Shouldn't be here.

                synchronizedMap.remove(key);
            } else {
                Object value = new Object();
                value.doAnotherExpensiveOperation(); // Shouldn't here.

                synchronizedMap.put(key, value);
            }
        }
    } // End of synchronization block.
}

Спасибо за помощь.

Ответы [ 6 ]

2 голосов
/ 26 октября 2011

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

public void doMapOperation(Object... keys) {
    ArrayList<Object> contained = new ArrayList<Object>();
    ArrayList<Object> missing = new ArrayList<Object>();

    synchronized (synchronizedMap) {
        if (synchronizedMap.containsKey(key)) {
            contained.add(synchronizedMap.get(key));
            synchronizedMap.remove(key);
        } else {
            missing.add(synchronizedMap.get(key));
            synchronizedMap.put(key, value);
        }
    }

    for (Object o : contained)
        o.doExpensiveOperation();
    for (Object o : missing)
        o.doAnotherExpensiveOperation();
}

Единственным недостатком является то, что вы можете выполнять операции со значениями после их удаления из synchronizedMap.

2 голосов
/ 26 октября 2011

Вы можете создать оболочку для synchronizedMap и убедиться, что такие операции, как containsKey, remove и put, являются синхронизированными методами.Тогда только доступ к карте будет синхронизирован, в то время как ваши дорогостоящие операции могут выполняться за пределами синхронизированного блока.

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

1 голос
/ 27 октября 2011

Если у вас нет нулевых значений в Map, вам вообще не нужен вызов containsKey(): вы можете использовать Map.remove(), чтобы удалить элемент и сообщить, был ли он там.Таким образом, истинное содержание вашего синхронизированного блока должно быть таким:

Object value = Map.remove(key);
if (value != null)
  value.doExpensiveOperation();
else
{
  value = new Value();
  value.doExpensiveOperation();
  map.put(key,value);
}

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

Object value = Map.remove(key);
if (value == null)
{
  value = new Value();
  map.put(key,value);
}
value.doExpensiveOperation();

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

1 голос
/ 27 октября 2011

Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла .Однако мы не должны упускать наши возможности в этих критических 3%.Такие рассуждения не приведут к самоуспокоенности хорошего программиста, он посмотрит внимательно на критический код;но только после того, как этот код был идентифицирован.- Дональд Кнут

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

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

1 голос
/ 26 октября 2011

На самом деле вы можете сделать все это только одним попаданием синхронизации.Первое удаление, вероятно, самое простое.Если вы знаете, что объект существует, и вы знаете, что удаление является атомарным, почему бы просто не удалить его, а если возвращаемое значение не равно null, вызвать дорогостоящие операции?

 // Remove key1 if it exists.
        if (synchronizedMap.containsKey(key1)) {
            Object value = synchronizedMap.remove(key1);
            if(value != null){ //thread has exclusive access to value 
              value.doExpensiveOperation();
            }
        }

Для пут, так как это дорого идолжно быть атомным, вам почти не повезло, и вам нужно синхронизировать доступ.Я бы порекомендовал использовать какую-то компьютерную карту.Взгляните на google-collection и MapMaker

. Вы можете создать ConcurrentMap, который будет строить дорогой объект на основе вашего ключа, например

 ConcurrentMap<Key, ExpensiveObject> expensiveObjects = new MapMaker()
       .concurrencyLevel(32)
       .makeComputingMap(
           new Function<Key, ExpensiveObject>() {
             public ExpensiveObject apply(Key key) {
               return createNewExpensiveObject(key);
             }
           });

Это простоформа памятка

В обоих случаях вам вообще не нужно использовать synchronized (хотя бы явно)

1 голос
/ 26 октября 2011

В первом фрагменте: объявите два значения из предложения if и просто назначьте их в предложении if. Сделайте предложение if синхронизированным и вызовите дорогостоящие операции снаружи.

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

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