Java &引用;原子地;更新整个阵列

Java &引用;原子地;更新整个阵列,java,performance,data-structures,concurrency,locking,Java,Performance,Data Structures,Concurrency,Locking,我有一个writer线程和一个reader线程来更新和处理数组池(存储在map中的引用)。写入与读取的比率几乎为5:1(写入延迟是一个问题) 编写器线程需要根据某些事件更新池中数组的几个元素。整个写操作(所有元素)都需要是原子的 我想确保,如果writer线程正在更新前一个更新的数组(类似volatile,但在整个数组而不是单个字段上),reader线程将读取该数组。基本上,我可以读取过时的值,但不能读取块 此外,由于写入非常频繁,因此在读/写时创建新对象或锁定整个阵列的成本非常高 是否有更高效

我有一个writer线程和一个reader线程来更新和处理数组池(存储在map中的引用)。写入与读取的比率几乎为5:1(写入延迟是一个问题)

编写器线程需要根据某些事件更新池中数组的几个元素。整个写操作(所有元素)都需要是原子的

我想确保,如果writer线程正在更新前一个更新的数组(类似volatile,但在整个数组而不是单个字段上),reader线程将读取该数组。基本上,我可以读取过时的值,但不能读取块

此外,由于写入非常频繁,因此在读/写时创建新对象或锁定整个阵列的成本非常高


是否有更高效的数据结构可以使用或使用更便宜的锁?

这个想法如何:writer线程不会改变数组。它只是对更新进行排队

每当读取器线程进入需要阵列稳定快照的读取会话时,它都会将排队更新应用于阵列,然后读取阵列

class Update
{
    int position;
    Object value;
}

ArrayBlockingQueue<Update> updates = new ArrayBlockingQueue<>(Integer.MAX_VALUE);

void write()
{
    updates.put(new Update(...));
}

Object[] read()
{
    Update update;
    while((update=updates.poll())!=null)
        array[update.position] = update.value;

    return array;
}
类更新
{
内部位置;
目标价值;
}
ArrayBlockingQueue updates=新的ArrayBlockingQueue(Integer.MAX_值);
无效写入()
{
更新。放置(新更新(…);
}
对象[]读取()
{
更新;
而((update=updates.poll())!=null)
数组[update.position]=update.value;
返回数组;
}

另一个想法,因为数组只包含20个双精度

有两个数组,一个用于写,一个用于读

读卡器在读取期间锁定读取数组

read()
    lock();
    read stuff
    unlock();
Writer首先修改write数组,然后tryLock读取数组,如果锁定失败,那么write()返回;如果锁定成功,请将写数组复制到读数组,然后释放锁

write()
    update write array
    if tryLock()
        copy write array to read array
        unlock()
读卡器可以被阻止,但仅限于复制20个双精度所需的时间,这是很短的

读取器应该使用自旋锁,比如
do{}while(tryLock()==false)以避免挂起

是否有更高效的数据结构

是的,绝对是!它们被称为持久数据结构。它们能够仅通过存储与以前版本的差异来表示矢量/地图/等的新版本。所有版本都是不可变的,这使得它们适合并发(编写器不会干扰/阻止读卡器,反之亦然)

为了表示更改,可以将对持久数据结构的引用存储在引用类型中,例如
AtomicReference
,并更改这些引用指向的内容,而不是结构本身

提供持久数据结构的顶级实现。它们是用纯、高效的Java编写的

下面的程序公开了如何使用持久数据结构来解决所描述的问题

import clojure.lang.IPersistentVector;
import clojure.lang.PersistentVector;

public class AtomicArrayUpdates {

    public static Map<Integer, AtomicReference<IPersistentVector>> pool
        = new HashMap<>();
    public static Random rnd = new Random();
    public static final int SIZE = 60000;
    // For simulating the reads/writes ratio
    public static final int SLEEP_TIMÉ = 5;

    static {        
        for (int i = 0; i < SIZE; i++) {
            pool.put(i, new AtomicReference(PersistentVector.EMPTY));
        }
    }

    public static class Writer implements Runnable {   
        @Override public void run() {
            while (true) {
                try {
                    Thread.sleep(SLEEP_TIMÉ);
                } catch (InterruptedException e) {}

                int index = rnd.nextInt(SIZE);
                IPersistentVector vec = pool.get(index).get();

                // note how we repeatedly assign vec to a new value
                // cons() means "append a value".
                vec = vec.cons(rnd.nextInt(SIZE + 1)); 
                // assocN(): "update" at index 0
                vec = vec.assocN(0, 42); 
                // appended values are nonsense, just an example!
                vec = vec.cons(rnd.nextInt(SIZE + 1)); 

                pool.get(index).set(vec);

            }
        }
    }

    public static class Reader implements Runnable {
        @Override public void run() {
            while (true) {
                try {
                    Thread.sleep(SLEEP_TIMÉ * 5);
                } catch (InterruptedException e) {}

                IPersistentVector vec = pool.get(rnd.nextInt(SIZE)).get();
                // Now you can do whatever you want with vec.
                // nothing can mutate it, and reading it doesn't block writers!
            }
        } 
    }

    public static void main(String[] args) {
        new Thread(new Writer()).start();
        new Thread(new Reader()).start();
    }
}
导入clojure.lang.IPersistentVector;
导入clojure.lang.PersistentVector;
公共类原子更新{
公共静态映射池
=新HashMap();
公共静态随机rnd=新随机();
公共静态最终整数大小=60000;
//用于模拟读/写比率
公共静态最终整数=5;
静态{
对于(int i=0;i
以下变体的灵感来源于和

编写器不会干扰/阻止读卡器,反之亦然,并且不存在线程安全性/可见性问题,也不存在微妙的推理

public class V2 {

    static Map<Integer, AtomicReference<Double[]>> commited = new HashMap<>();
    static Random rnd = new Random();

    static class Writer {
        private Map<Integer, Double[]> writeable = new HashMap<>();
        void write() {        
            int i = rnd.nextInt(writeable.size());   
            // manipulate writeable.get(i)...
            commited.get(i).set(writeable.get(i).clone());
        }
    }

    static class Reader{
        void read() {
            double[] arr = commited.get(rnd.nextInt(commited.size())).get();
            // do something useful with arr...
        } 
    }

}
公共类V2{
静态映射committed=newhashmap();
静态随机rnd=新随机();
静态类编写器{
私有映射可写=新HashMap();
void write(){
int i=rnd.nextInt(writeable.size());
//操作可写。获取(i)。。。
committed.get(i).set(writeable.get(i.clone());
}
}
静态类读取器{
无效读取(){
double[]arr=committed.get(rnd.nextInt(committed.size()).get();
//用arr做一些有用的事情。。。
} 
}
}
  • 您需要两个静态引用:
    readArray
    writeArray
    以及一个简单的互斥锁,以便在写入更改时进行跟踪

  • 使用名为changeWriteArray的锁定函数对writeArray的deepCopy进行更改:

    同步字符串[]changeWriteArray(字符串[]writeArrayCopy,其他参数转到此处){ //这是我的
          //then return deepCopy
          return writeArrayCopy;
    }
    
    private final Map<Key, double[]> map = new HashMap<> ();
    
    public synchronized void write(Key key, double value, int index) {
        double[] array = map.get(key);
        array[index] = value;
    }
    
    public synchronized double[] read(Key key) {
        return map.get(key);
    }
    
    //If all the keys and arrays are constructed before the writer/reader threads 
    //start, no need for a ConcurrentMap - otherwise use a ConcurrentMap
    private final Map<Key, AtomicReference<double[]>> map = new HashMap<> ();
    
    public void write(Key key, double value, int index) {
        AtomicReference<double[]> ref = map.get(key);
        double[] oldArray = ref.get();
        double[] newArray = oldArray.clone();
        newArray[index] = value;
        //you might want to check the return value to see if it worked
        //or you might just skip the update if another writes was performed
        //in the meantime
        ref.compareAndSet(oldArray, newArray);
    }
    
    public double[] read(Key key) {
        return map.get(key).get(); //check for null
    }
    
    public synchronised enqeue(Write write);
    
    public synchronised Element cut();
    
    public class MolecularArray {
        private final double[] writeArray;
        private final double[] sharedArray;
        private final double[] readArray;
    
        private volatile boolean writerOwnsShared;
    
        MolecularArray(int length) {
            writeArray = new double[length];
            sharedArray = new double[length];
            readArray = new double[length];
        }
    
        void read(Consumer<double[]> reader) {
            if (!writerOwnsShared) {
                copyFromTo(sharedArray, readArray);
                writerOwnsShared = true;
            }
            reader.accept(readArray);
        }
    
        void write(Consumer<double[]> writer) {
            writer.accept(writeArray);
            if (writerOwnsShared) {
                copyFromTo(writeArray, sharedArray);
                writerOwnsShared = false;
            }
        }
    
        private void copyFromTo(double[] from, double[] to) {
            System.arraycopy(from, 0, to, 0, from.length);
        }
    }