同步实例而不是volatile基元

时间:2017-08-03 19:16:56

标签: java multithreading memory-management thread-safety

我有一个对象:

class Data{
   int a;
   int b;
   AtomicIntegerArray c = new AtomicIntegerArray(10000);   
}

在以下场景中,线程A和B使用此对象:

A创建数据(现在称为"数据")

A将数据发送到队列。

B从队列中读取数据。

B将数据的字段和信号更新为A已处理的数据。

B睡觉。

A开始处理数据字段。

现在,我到目前为止所做的工作是确保内存可见性:

 class Data{
   volatile int a;
   volatile int b;
   volatile AtomicIntegerArray c = new AtomicIntegerArray(10000);   
}

这有效,但让我很担心。当线程A获取数据时,它只需要在开始时将其同步一次,而不是每次接触一个字段时。我想我可以通过简单地做A来实现这个目标:

synchronized(data){}

一旦知道数据已经更新,就可以使用Data的第一个实现。这样我只需要进行一次昂贵的内存同步。

我说错了吗?我是否还需要确保线程B在将数据交付之前同步数据"线程A?

请记住,我只对内存同步/可见性感兴趣,没有锁定机制,并且线程之间的信令无关紧要。我有这个。

SIGNALING:

class A implements callback{
    private volatile boolean dataProcessed;
    private final Data data = new Data();

    @Override
    public void dataHasBeenProcessed(){
         dataProcessed = true;
    } 

    void someMethod(){
       dataProcessed = false;
       threadB.processData(data, this);
       while(!dataProcessed)
          ...sleep;
       data.workOnFields();

}

因此,A将数据发送给B,然后在处理数据时轮询一个易失性布尔值,B在回调方法中设置。

2 个答案:

答案 0 :(得分:1)

TL; DR 您根本不需要volatilesynchronized

线程A和B从不同时触摸对象,因此只要它们之间的切换建立发生在边界之外,它们将始终看到最新数据。

例如,如果队列是BlockingQueue,则可以获得此保证:

  

内存一致性效果:与其他并发集合一样,在将对象放入BlockingQueue 之前,线程中的操作发生在之后的操作中,该操作是在访问或删除该元素之后执行的另一个帖子中的BlockingQueue

因此,只要队列是BlockingQueue,而不是通用Queue,从线程A到线程B的切换就是安全的。

如果来自线程B的信号回到线程A正在使用例如一个CountDownLatch,你得到这个保证:

  

内存一致性影响:在计数达到零之前,在调用countDown() 之前的某个线程中的操作发生在之后的操作发生之后,从另一个中的相应await()成功返回线程。

因此从线程B切换回线程A也是安全的。

<强>要点:

  • 线程A在发送对象之前线程B收到对象之前发生

  • 发送信号之前,线程B执行之前

<强>结论:

不需要volatilesynchronized

答案 1 :(得分:0)

  

我说错了吗?我是否还需要确保线程B在将数据交付之前同步数据&#34;线程A?

由于您已使用volatile变量建立发生之前您是正确的。如果线程B写入 volatile 变量dataProcessed,它将使线程A可以读取相同的volatile变量的所有内存更改(到Data)。

由于您询问内存可见性,请以这种方式考虑:当线程B写入volatile变量时,保证刷新它更改的所有变量(Data)主要记忆。 (从线程B运行的CPU核心)。线程A现在可以看到它。这是发生在以前的关系的本质。

虽然它有效,但这不是一个好的设计。您有一个由多个线程共享的对象(Data)。该对象应该是线程安全的。假设某人将来添加了一个Thread C并忘记强制执行之前发生的关系。这将导致难以找到通常需要很长时间才能识别的同步错误,因为它们间歇性地或很少发生。