锁定自由数组元素交换

时间:2009-08-05 08:40:10

标签: java multithreading concurrency locking deadlock

在多线程环境中,为了进行线程安全的数组元素交换,我们将执行同步锁定。

// 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 个答案:

答案 0 :(得分:6)

无论使用哪种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);

希望有道理。

更新:

一些测试证明,除非您有大量线程以相同的方式访问阵列(在普通硬件上100多个),否则简单的同步(数组){}的工作速度要比精心同步快得多。

答案 1 :(得分:1)

  // 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;
    }
  }

答案 2 :(得分:0)

您最接近的是java.util.concurrent.atomic.AtomicReferenceArray,它提供基于CAS的操作,例如boolean compareAndSet(int i, E expect, E update)。虽然它没有swap(int pos1, int pos2)操作,但您必须通过两次compareAndSet调用来模拟它。

答案 3 :(得分:0)

“并发应用程序中可伸缩性的主要威胁是独占资源锁定。” - Java Concurrency in Practice。

我认为你需要一个锁,但正如其他人所说,锁可以比现在更精细。

您可以像java.util.concurrent.ConcurrentHashMap一样使用锁定条带化。

答案 4 :(得分:0)

您提到的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不会这样做,因为没有用于保留值的独立结构(或者您无权访问它们)。

答案 5 :(得分:-1)

我不认为AtomicReferenceFieldUpdater是用于数组访问的,即使它是,它也只是一次为一个引用提供原子保证。 AFAIK,java.util.concurrent.atomic中的所有类一次只提供对一个引用的原子访问。为了将两个或多个引用更改为一个原子操作,您必须使用某种锁定。