【问题标题】:Writing to two variables atomically if a condition is satisfied如果满足条件,则以原子方式写入两个变量
【发布时间】:2015-11-13 08:07:37
【问题描述】:

我有以下将被多个线程使用的类:

public class Container

    private volatile Date date;
    private int amount;
    private final Object lock = new Object();

    public void update(int amount){
        int actualAmount;
        if(check(date)){
              //do some BULK computation to compute the actualAmount
             synchronized(lock){
                 date = new Date();
                 this.amount = actualAmount;
             }
        }
    }

    private boolean check(Date date){
        synchronized(lock){
          //reading from the date and returning true if the date is expired
        }
    }
}

但我不确定正确性。首先,我将Date 字段声明为volatile,以便在执行条件检查时观察变化。但可能会发生在进行批量计算时,第二个线程试图执行更新导致数据竞争。

我不想将BULK 计算放入同步块中,因为它包括调用几个外来方法并阻止 JVM 进行优化。

执行两次批量计算不会对数据结构造成损害,但会浪费处理器的时间。

我应该如何以更有效的方式处理这个问题?

【问题讨论】:

    标签: java multithreading synchronization


    【解决方案1】:

    当批量计算完成并且另一个线程在此期间更改了数量时,我们别无选择,只能重新计算数量(重新运行此批量操作),是吗?

        public void update(int amount) {
        int actualAmount;
        if (check(date)){
              //do some BULK computation to compute the actualAmount
             synchronized(lock) {
                if (check(date)) {
                     date = new Date();
                     this.amount = actualAmount;
                } else {
                    update(amount);
                }
             }
        }
    }
    

    当两个线程执行这个批量操作并且其中一个应该重新运行它时,这不是浪费 CPU 时间吗?

    累积此更新(数量)可能更有效,当某些线程需要读取正确的数量时,然后重新计算它(运行批量操作)。

    什么线程会读到这些?它需要最新的信息吗? 知道答案可能有助于设计更好的解决方案。

    更新 1。 顺便说一句,这种递归重新计算可能需要一遍又一遍。所以最正确的方法是将整个 update() 主体包装到同步语句中。

    【讨论】:

      【解决方案2】:

      类似这样的:

      public class Container
      
          private Date date;
          private int amount;
          private final Object lock = new Object();
      
          public void update(int amount){
              int actualAmount;
              Date oldDate;
              // Loop until no one call update() during our computations
              for(oldDate = checkDate(); oldDate != null; oldDate = checkDate()){
                  //do some BULK computation to compute the actualAmount
                  synchronized(lock){
                      if(date == oldDate){
                          // No one update data inbetween
                          date = new Date();
                          this.amount = actualAmount;
                          break;
                      }
                  }
              }
          }
      
          /* Return this.date if it is expired or null otherwise */
          private Date checkDate(void){
              synchronized(lock){
                // Check .date and return appropriate value
              }
          }
      }
      

      如果它过期,我们在这里存储.date 的值。这样我们就可以在我们执行 BULK 计算时检查是否有人更新了数据。如果他有,我们重复数据检查和计算。

      如果必须在checkDate() 中取lock,则无需将.date 声明为volatile。如果您能够在 不锁定 的情况下检查日期,您可能(取决于实际算法)应该恢复 volatile 修饰符。

      请注意,如果您预计 update() 可能会被非常密集地调用,因此在 BULK 计算期间很有可能会更新数据,您需要在某个时候中断循环并执行这些计算 锁定:性能影响很差,但饥饿更糟。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-01
        • 1970-01-01
        • 2013-08-08
        • 2023-04-05
        • 1970-01-01
        相关资源
        最近更新 更多