在Java中,如何确保布尔标志的安全和一致的并发使用,同时最大限度地减少对时间性能的影响?

在Java中,如何确保布尔标志的安全和一致的并发使用,同时最大限度地减少对时间性能的影响?,java,multithreading,atomic,volatile,Java,Multithreading,Atomic,Volatile,在我的场景中,我有DirtyArray对象,它们基本上是原始数组包装器,在发生写访问时设置布尔“dirty”标志 公共类目录数组{ 专用字节[]数据; 公共目录数组(字节[]数据){ 这个数据=数据; } 私有布尔脏=假; public void setValue(int索引,字节值){ 肮脏=真实; 数据[索引]=值; } 公共布尔值isDirty(){ 返脏; } } 脏标志仅从false变为true 我需要使它安全地同时使用: 有一个或多个线程可以修改数组(setValue)。 有一个或

在我的场景中,我有
DirtyArray
对象,它们基本上是原始数组包装器,在发生写访问时设置布尔“dirty”标志

公共类目录数组{
专用字节[]数据;
公共目录数组(字节[]数据){
这个数据=数据;
}
私有布尔脏=假;
public void setValue(int索引,字节值){
肮脏=真实;
数据[索引]=值;
}
公共布尔值isDirty(){
返脏;
}
}
脏标志仅从
false
变为
true

我需要使它安全地同时使用: 有一个或多个线程可以修改数组(
setValue
)。 有一个或多个线程在GCed之前捕获
DirtyArray
,如果它已被修改(
isDirty
),则应该将其注销到磁盘

现在,如果我理解正确,像上面这样做是不安全的: 实际上,从
isDirty
线程的角度来看,
data[index]=value
存储可以在
dirty=true
存储之前重新排序。 因此,查看
isDirty()==false
并不保证数据未被修改

这是正确的吗

假设是,那么将
标记设置为dirty
标志
volatile
可以解决此问题。
然而,在下面的基准测试中,我看到这样做时会减速50-100倍

@基准测试
@基准模式(模式平均时间)
@输出时间单位(时间单位纳秒)
公众假期
{
for(int i=0;i
将AtomicBoolean与Java 9中引入的内存排序get/set变量一起使用,我得到了以下变量:

公共类目录数组{
专用字节[]数据;
公共目录数组(字节[]数据){
这个数据=数据;
}
私有AtomicBoolean dirty=新的AtomicBoolean();
public void setValue(int索引,字节值){
如果(!dirty.getPlain())
dirty.setRelease(true);
数据[索引]=值;
}
公共布尔值isDirty(){
返回dirty.getAcquire();
}
}
其性能(在上述基准测试中)与原始非易失性版本相同

这样做安全吗?也就是说,它是否保证当修改
数据时,我将看到
isDirty()==true
? (在我看来应该是这样,但这只是因为
dirty
只会从
false
变为
true
,永远不会返回。)

是否有其他变体可以实现此保证,甚至可能允许将
重置为
, 理想情况下不会对性能产生负面影响


更新 我同意到目前为止对答案的一般评估,即保证更改的
数据
数组和
标志之间一致性的唯一方法是同步
设置值
isDirty
。指出的比赛条件才是真正的问题,与其说是让脏旗变得可见。所以基本上我上面问的问题是错误的

有关更多上下文:这是关于在中存储透明缓存图像的像素。它在非常紧密的循环中使用,而从同步中获得成功并不是一个真正的选项。典型的使用场景是:

  • 多个线程修改图像(由许多
    dirtyarray
    支持)
  • isDirty()
    检查发生在另一个捕获的线程上 垃圾收集之前(
    PhantomReference
    DirtyArray
    )的持有者,若它是脏的,则将其写入磁盘
我现在的观点是,这应该在比单个
setValue()
调用更粗糙的层次上进行。由于线程通过从
ConcurrentHashMap
获取而在
DirtyArray
之间切换,因此会出现一些“自然”的同步点(当然会忽略细节),线程位于线程池中并从共享队列中获取作业,或者线程以其他方式相互等待。在这些同步点,早期(按程序顺序)
setValue()
s的效果必须可见。因此,我倾向于只使用普通的非同步版本,并依赖于粗略级别的同步


唯一让我有点头疼的是清理是由垃圾收集触发的,我必须确保(DirtyArray
的持有者)在粗同步点之前没有被收集。但我认为我可以通过保留强引用并在必要时添加来确保这一点。

原子布尔(或原子家族的任何其他成员)不能保证与不同变量的同步。所以不是。代码不能保证当数据被修改时,您将得到isDirty()==true。唯一可以保证的是,所有线程总是看到相同的isDirty()值。事实上,上述两种选择都不能保证这一点


保证的唯一方法是对set方法中的整个代码块进行独占锁定:if语句和赋值。这可以通过synchronized关键字(在方法上或在代码块中)或使用
java.util.concurrency
中的锁定机制来实现。您可以尝试使用
AtomicReferenceArray
(请参阅文档)。 此阵列为您提供并发访问方面的保证

我发现了一个使用示例,我将其复制粘贴到此处供您尝试:

import java.util.concurrent.atomic.AtomicReferenceArray;

public class TestThread {
   private static String[] source = new String[10];
   private static AtomicReferenceArray<String> atomicReferenceArray 
      = new AtomicReferenceArray<String>(source);

   public static void main(final String[] arguments) throws InterruptedException {

      for (int i = 0; i<atomicReferenceArray.length(); i++) {
         atomicReferenceArray.set(i, "item-2");
      }

      Thread t1 = new Thread(new Increment());
      Thread t2 = new Thread(new Compare());
      t1.start();
      t2.start();

      t1.join();
      t2.join();        
   }  

   static class Increment implements Runnable {
      
      public void run() {
         
         for(int i = 0; i<atomicReferenceArray.length(); i++) {
            String add = atomicReferenceArray.getAndSet(i,"item-"+ (i+1));
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ add);
         }
      }
   }

   static class Compare implements Runnable {
      
      public void run() {
         
         for(int i = 0; i<atomicReferenceArray.length(); i++) {
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ atomicReferenceArray.get(i));
            boolean swapped = atomicReferenceArray.compareAndSet(i, "item-2", "updated-item-2");
            System.out.println("Item swapped: " + swapped);
            
            if(swapped) {
               System.out.println("Thread " + Thread.currentThread().getId() 
                  + ", index " +i + ", updated-item-2");
            }
         }
      }
   }
}

关于使用
AtomicBoolean
解决方案的几点注意事项

您需要什么样的线程安全性?我不明白你为什么那么在乎
Thread 9, index 0, value: item-2
Thread 10, index 0, value: item-1
Item swapped: false
Thread 10, index 1, value: item-2
Item swapped: true
Thread 9, index 1, value: updated-item-2
Thread 10, index 1, updated-item-2
Thread 10, index 2, value: item-3
Item swapped: false
Thread 10, index 3, value: item-2
Item swapped: true
Thread 10, index 3, updated-item-2
Thread 10, index 4, value: item-2
Item swapped: true
Thread 10, index 4, updated-item-2
Thread 10, index 5, value: item-2
Item swapped: true
Thread 10, index 5, updated-item-2
Thread 10, index 6, value: item-2
Thread 9, index 2, value: item-2
Item swapped: true
Thread 9, index 3, value: updated-item-2
Thread 10, index 6, updated-item-2
Thread 10, index 7, value: item-2
Thread 9, index 4, value: updated-item-2
Item swapped: true
Thread 9, index 5, value: updated-item-2
Thread 10, index 7, updated-item-2
Thread 9, index 6, value: updated-item-2
Thread 10, index 8, value: item-2
Thread 9, index 7, value: updated-item-2
Item swapped: true
Thread 9, index 8, value: updated-item-2
Thread 10, index 8, updated-item-2
Thread 9, index 9, value: item-2
Thread 10, index 9, value: item-10
Item swapped: false
    public void setValue(int index, byte value) {
        data[index] = value;
        dirty = true;
    }
public class SyncronizedDirtyArray {
    private byte[] data;

    public SyncronizedDirtyArray (byte[] data) {
        this.data = data;
    }

    private boolean dirty = false;

    public synchronized void setValue(int index, byte value) {
        dirty = true;
        data[index] = value;
    }

    public synchronized boolean isDirty() {
        return dirty;
    }
}
org.example.sync.DirtyArrayNoSync: 112
org.example.sync.DirtyArraySync: 2222
org.example.sync.DirtyArrayLock: 16752
org.example.sync.DirtyArrayVolatile: 7555
org.example.sync.DirtyArrayAtomic: 7591
org.example.sync.DirtyArrayAtomicSet: 3066
org.example.sync.DirtyArrayNoSync: 102
org.example.sync.DirtyArraySync: 2323
org.example.sync.DirtyArrayLock: 16801
org.example.sync.DirtyArrayVolatile: 7942
org.example.sync.DirtyArrayAtomic: 7984
org.example.sync.DirtyArrayAtomicSet: 3320
class Counter {
  volatile int counter = 0;

  void incrementCounter() {
    counter = counter + 1; 
  } 
}
if (!da.isDirty()) {
    da.setValue(...);
}
public void setValue(int index, byte value) {
    if (dirty) {
        data[index] = value;
    }
    else {
        synchronized(this) {
            dirty = true;
            data[index] = value;
        }
    }
}
void fill(byte value, int offset, int length);
void setValue(byte[] source, int offset, int length);
public class DirtyArray {
    private final Cell[] cells;

    public DirtyArray(byte[] data) {
        cells = new Cell[data.length];
        Arrays.setAll(cells, i -> new Cell(data[i], false));
    }

    public void setValue(int index, byte value) {
        Cell cell = cells[index];
        if (cell.value != value || !cell.dirty) {
            cells[index] = new Cell(value, true);
        }
    }

    public boolean isDirty() {
        for (Cell cell : cells) {
            if (cell.dirty) {
                return true;
            }
        }
        return false;
    }

    private static class Cell {
        final byte value;
        final boolean dirty;

        Cell(byte value, boolean dirty) {
            this.value = value;
            this.dirty = dirty;
        }
    }
}