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