Блокировка свободной замены элементов массива - PullRequest
3 голосов
/ 05 августа 2009

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

// a is char array.
synchronized(a) {
    char tmp = a[1];
    a[1] = a[0];
    a[0] = tmp;
}

Возможно ли, что мы можем использовать следующий API в описанной выше ситуации, чтобы у нас была возможность замены элементов массива без блокировки? Если да, то как?

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.html#compareAndSet%28T,%20V,%20V%29

Ответы [ 6 ]

5 голосов
/ 05 августа 2009

Независимо от используемого API, вы не сможете добиться как поточнобезопасного, так и свободного от блокировки обмена элементов массива в Java.

Замена элемента требует нескольких операций чтения и обновления, которые должны выполняться атомарно. Для симуляции атомности вам нужен замок.

РЕДАКТИРОВАТЬ:

Альтернативой алгоритму без блокировки может быть микроблокировка: вместо блокировки всего массива можно блокировать только те элементы, которые меняются местами.

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

В противном случае, когда разные потоки могут попытаться поменять местами перекрывающиеся элементы, тогда порядок выполнения потоков будет иметь значение. Например, если один поток пытается поменять местами элементы 0 и 1 массива, а другой одновременно пытается поменять местами 1 и 2, то результат будет полностью зависеть от порядка выполнения, для начальных {'a', 'b', 'c '} вы можете получить либо {' b ',' c ',' a '} или {' c ',' a ',' b '}. Следовательно, вам потребуется более сложная синхронизация.

Вот быстрый и грязный класс для массивов символов, который реализует микроблокировку:

import java.util.concurrent.atomic.AtomicIntegerArray;

class SyncCharArray {

    final private char array [];
    final private AtomicIntegerArray locktable;

    SyncCharArray (char array[])
    {
      this.array = array;

      // create a lock table the size of the array
      // to track currently locked elements 
      this.locktable = new AtomicIntegerArray(array.length);
      for (int i = 0;i<array.length;i++) unlock(i);

    }

    void swap (int idx1, int idx2)
    {
      // return if the same element
      if (idx1==idx2) return;

      // lock element with the smaller index first to avoid possible deadlock
      lock(Math.min(idx1,idx2));
      lock(Math.max(idx1,idx2));

      char tmp = array[idx1];
      array [idx1] = array[idx2];
      unlock(idx1);
      array[idx2] = tmp;
      unlock(idx2);

    }

    private void lock (int idx)
    {
      // if required element is locked when wait ...
      while (!locktable.compareAndSet(idx,0,1)) Thread.yield();
    }

    private void unlock (int idx)
    {
      locktable.set(idx,0);
    }

}

Вам нужно создать SyncCharArray, а затем передать его всем потокам, которые требуют замены:

char array [] = {'a','b','c','d','e','f'};
SyncCharArray sca = new SyncCharArray(array);

 // then pass sca to any threads that require swapping
 // then within a thread

sca.swap(15,3);

Надеюсь, в этом есть какой-то смысл.

UPDATE:

Некоторое тестирование показало, что, если у вас нет большого количества потоков, одновременно обращающихся к массиву (более 100 на обычном оборудовании), простая синхронизация (массив) {} работает намного быстрее, чем сложная синхронизация.

1 голос
/ 19 мая 2016
  // lock-free swap array[i] and array[j] (assumes array contains not null elements only)
  static <T> void swap(AtomicReferenceArray<T> array, int i, int j) {
    while (true) {
      T ai = array.getAndSet(i, null);
      if (ai == null) continue;
      T aj = array.getAndSet(j, null);
      if (aj == null) {
        array.set(i, ai);
        continue;
      }
      array.set(i, aj);
      array.set(j, ai);
      break;
    }
  }
0 голосов
/ 06 августа 2009

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

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

Если вам необходимо использовать массив, его можно изменить, чтобы он содержал обновляемые объекты (не примитивные типы или Char) и синхронизировал оба из них при обмене. S структура данных, как это будет работать:

public class CharValue {
    public char c;
}

CharValue[] a = new CharValue[N];

Не забудьте использовать детерминированный порядок синхронизации, чтобы не было взаимоблокировок (http://en.wikipedia.org/wiki/Deadlock#Circular_wait_prevention)! Вы можете просто следовать порядку индексов, чтобы избежать его.

Если элементы также должны быть добавлены или удалены одновременно из коллекции, вы можете вместо этого использовать Map, синхронизировать свопы на Map.Entry и использовать синхронизированную реализацию Map. Простой List не будет этого делать, потому что нет изолированных структур для хранения значений (или у вас нет к ним доступа).

0 голосов
/ 06 августа 2009

«Главная угроза масштабируемости в параллельных приложениях - исключительная блокировка ресурса». - Параллелизм Java на практике.

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

Вы можете использовать чередование блокировок, например, java.util.concurrent.ConcurrentHashMap.

0 голосов
/ 05 августа 2009

Самое близкое, что вы получите, это java.util.concurrent.atomic.AtomicReferenceArray , который предлагает операции на основе CAS, такие как boolean compareAndSet(int i, E expect, E update). У него нет операции swap(int pos1, int pos2), поэтому вам придется эмулировать его двумя вызовами compareAndSet.

0 голосов
/ 05 августа 2009

Я не думаю, что AtomicReferenceFieldUpdater предназначен для доступа к массиву, и даже если бы он был, он обеспечивает атомарные гарантии только для одной ссылки за раз. AFAIK, все классы в java.util.concurrent.atomic обеспечивают атомарный доступ только к одной ссылке за раз. Чтобы изменить две или более ссылок как одну атомарную операцию, вы должны использовать какую-то блокировку.

...