并发读/写整型数组的快速实现

3
我需要一种快速的方式来并发地读取/写入一个int数组。写操作是按索引进行的。只有在所有写操作完成后,才需要对整个数组进行读操作。
我开始尝试使用同步版本:
public final class SynchronizedIntArray {
    private final int[] elements = new int[1000000];

    public synchronized void set(int index, int value)
    {
        elements[index] = value;
    }

    public synchronized int[] getAll()
    {
        return elements;
    }
}

性能不好。 我在网上搜索更好的解决方案,并找到了AtomicIntegerArray

public final class ConcurrentIntArray
{
    private final AtomicIntegerArray elements = new AtomicIntegerArray(1000000);

    public void set(int index, int value)
    {
        elements.set(index, value);
    }

    public int[] getAll()
    {
        int[] intArray = new int[elements.length()];
        for (int i = 0; i < intArray.length(); i++) {
            intArray[i] = elements.get(i);
        }
        return intArray;
    }
}

但我想知道为什么没有一种方法可以一次性获取完整的int数组,而不是从AtomicIntegerArray中逐个获取。

尽管这种方式的性能仍然比同步版本要好得多,但我真的需要以这种方式复制吗?

对于我的问题,是否有比AtomicIntegerArray更快的替代方法?


你说“性能不好”,具体哪里不好呢?我觉得你不会比这个更好:elements[index] = value;,可以看看 AtomicIntegerArray 的代码,看看他们是怎么做的。 - Scary Wombat
@user2310289 实际上,根据源代码,他们正在使用sun.misc.Unsafe类将存储外包给本地方法,这取决于实现方式,可能会更快或更慢... - Sinkingpoint
@user2310289:性能的好处不在于数组赋值,而在于避免使用synchronized,因为它会锁定整个数组以进行单个赋值。 - Louis Wasserman
谢谢 - 我应该听取自己的建议。 - Scary Wombat
3个回答

4

是的,你真的需要以这种方式复制它。 AtomicIntegerArray 的实现意味着没有办法锁定整个数组并在没有其他线程同时进行更改的情况下获取它:你只能原子地逐个获取一个元素。


1

我不是Java程序员,但作为实时程序员,我认为你们两个的实现在概念上都是错误的。
虽然我可能误解了Java同步的工作方式。如果是这种情况,请忽略此帖子并接受我的道歉。

您的第一个解决方案

确保没有写入者之间的并发,因为您锁定了访问唯一允许修改一个元素的函数。
您还锁定了整个数组的访问,但仅限于读取器之间(这是无用的,因为读取器不会修改数据,因此可以安全地同时读取数组)。

但是,您没有防止读取器和写入器之间的并发访问。一个(单个)读取器可能会读取正在被(单个)写入器修改的元素的值。

此外,您返回对成员数组的引用而不是副本,因此调用者将访问实际数据,这些数据很可能随时被写入者覆盖。
因此,在调用读取访问器之后,数组根本没有受到保护,以防止在写入期间进行读取访问。

如果访问数组元素的实际操作是原子性的(我非常怀疑,但再一次地,Java不是我的强项),那么代码可能会起作用;或者更有可能的是,潜在的不一致性很少并且不容易被检测到。如果您在不同的环境下运行或甚至稍微更改代码以增加并发读/写的出现次数或变得更容易受到这些不一致性的影响,则您的应用程序可能会出现问题。

您的第二种解决方案

使用AtomicArray,它与您之前的代码相同,但添加了对单个数组值并发读取的保护。
它消除了第一种解决方案的潜在不一致性(也因为这次您的读取函数被强制返回实际数组的副本),但是读取整个数组效率非常低,因为您的AtomicArray对象将获取并释放每个读取值的锁。

一个可能的方法

您必须保护整个数组。

在一般情况下,您可能希望使用读者/写入器锁。这将允许多个读取器同时访问数组。
但是,如果我理解正确,在您的情况下只有一个读取器,因此最好使用简单锁。

您的单次写入和全局读取的代码如下:
public final class SynchronizedIntArray {
    private final int[] elements = new int[1000000];
    private final Lock lock = new ReentrantLock();

    public void set(int index, int value)
    {
        lock.lock();
        elements[index] = value;
        lock.unlock();
    }

    public int[] getAll()
    {
        int[] copy;
        lock.lock();
        copy = elements.clone();
        lock.unlock();
        return copy;
    }
}

-1

是的,有一个非常好的实现比AtomicIntegerArray更适合您的用例。这里的关键是引用和整数分配是原子的。volatile写操作不会被编译器优化。

//This class is thread safe 
public final class BetterIntArray {

private volatile int[] elements = new int[1000000]; // Don't make it finall

public void set(int index, int value)
{
    elements[index] = value; //assignment is atomic
    elements =  elements; // guarantees that changes are visible to other thread
}

public int[] getAll()
{
    return elements ;
}    

}


请只翻译内容:请在下投票的同时也注明原因。 - veritas
对数组elements[index]的赋值并不是真正的原子操作(volatile)。只有对elements变量本身的赋值是volatile的。例如,参见这里 - Mifeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接