【问题标题】:Atomically update multiple volatile and j.u.c.atomic variables以原子方式更新多个 volatile 和 j.u.c.atomic 变量
【发布时间】:2015-01-02 18:31:32
【问题描述】:

为了以原子方式更新两个或更多 volatile 变量,是否需要使用带同步、可重入读写锁等的锁来保护它?

volatile int vVar1, vVar1; // or AtomicInteger

/*** Needs to be updated atomically ***/
void atomicUpdate(int var1, int var2){
  vVar1 = var1;
  vVar2 = var2;
}

同样的代码用于 java.util.concurrent.atomic 变量。

【问题讨论】:

  • S简短的回答,是的,现在这似乎是一个 XY problem,所以你为什么不退后一步告诉我们你想做什么?
  • 我想在多线程环境中自动更新我的两个变量。
  • 我说退一步。您有两个 volatile 变量的事实表明您的底层设计似乎存在问题。因此,我的建议是退后一步,告诉我们你想做什么。
  • 是的,volatile 保证可见性并在 var 写入与其后续读取之间的关系之前建立。它与两个不同内存位置上操作的原子性没有任何关系。

标签: java multithreading scala atomic volatile


【解决方案1】:

如果您需要以原子方式分配两个值,将 volatile int 更改为 AtomicInteger 将无法解决您的竞争条件问题。

要解决您的问题,您基本上有两种选择:

  1. 使方法更新变量synchronized(也可能是读取这些变量的方法)
  2. 为您的两个变量创建一个包装器,并利用赋值是原子操作这一事实

选项 2 的示例:

volatile Vars vars;
void atomicUpdate(int var1, int var2) {
    vars = new Vars(var1, var2);
}

public static Vars {
    private int vVar1;  // volatile if they need to be modified
    private int vVar2;
}

我更喜欢选项 2,因为它是非阻塞的,并且允许您缓存任何类型的数据。

【讨论】:

  • 回复:“......也许还有读取这些变量的方法......”不。不是也许。仅同步更新变量的代码将什么都不做以防止其他代码查看它们处于不一致的状态。
  • 这取决于如何读取变量。如果它们从不一起阅读,那没关系;-)
  • 您的选项 2 与 wha'eve 的答案存在相同的问题:这只是解决方案的一半。您可以“原子地”为vars 分配一个新对象引用,但这并不能保护首先查看vars.vVar1 然后查看vars.vVar2 的另一个线程。
  • 再次,这取决于阅读部分。如果你这样做了:Vars vars = c.getVars(); int v1 = vars.getV1(); int v2 = vars.getV2(),你就很好,即使c 中的vars 可能已被替换。
  • “同样取决于阅读部分......”这就是为什么我称之为“一半解决方案”阅读部分是解决方案的另一半。在你给他两半之前,你还没有给这个菜鸟一个完整的解决方案。这就是我关于需要同步读取线程和写入线程的观点。假设 没有阅读线程关心两个变量之间的关系(无论它是什么),这是假设一种特殊情况。但是如果你不知道特殊情况是否有效,那么应该告诉菜鸟一般情况。
【解决方案2】:

创建一个封装所有状态变量的类,然后使用AtomicReference 来引用它们。这缓解了线程需要安全地设置/检查多个值的竞争条件。

// set initial state
AtomicReference<MyState> ref = new AtomicReference<MyState>();
ref.set(new MyState("abc", "def"));

// .. Thread 1 needs to change the state:
ref.set(new MyState("xyz", "def"));

// .. Thread 2 needs to read the state (consistently):
MyState state = ref.get();
if ("test1".equals(state.a)) { }
else if ("test2".equals(state.b) { }

这里的好处是Thread 2 能够从同一个MyState 实例中一致地读取MyState.aMyState.b,而不是让MyState 实例变量引用检查之间的更改。

【讨论】:

    【解决方案3】:

    我想自动更新我的两个变量

    你不能。 Java 语言或 Java 标准库中没有跨越多个变量的原子操作。

    您可能可以使用synchronized 关键字来解决您的问题,但是使用synchronized 与使用原子不同,因为为了使其工作,线程必须相互合作

    如果这两个变量之间必须始终存在特定的关系(也就是不变量),并且如果您无法在不临时打破不变量的情况下更新变量 em>,那么您必须同步进行更新的代码,并且您还必须同步所有其他期望不变量为真的代码块。

    那是因为,当你写这个时:

    synchronized(foo) { ... }
    

    它不会阻止其他线程做任何事情,除了同时在同一个对象上同步。


    另请注意:一旦您正确同步了对变量的所有访问权限,您就不需要将它们设为volatile。这是因为无论一个线程在释放锁之前写入内存的任何内容,都保证对随后获取相同锁的任何其他线程可见。

    【讨论】:

      【解决方案4】:

      另一种方法是使用 volatile 数组:

      private volatile int[] var;
      
      void update (int var1, int var2) {
        var = new int[] { var1, var2 };
      }
      

      这将是原子的,但它假定您的其余代码不直接访问 vVar1 和 vVar2。根据您要实现的目标,可能会有更好的选择 - 例如,您可以为两个整数创建一个临时线程安全持有者类(通常是不可变的)。

      【讨论】:

      • 如果其他线程中的代码检查var[0],然后检查var[1],会怎样?如果在这两个操作之间,第一个线程“原子地”更新var(即,将其设置为指向一个新数组)怎么办?你只给出了一半的解决方案。另一半是第二个线程存储它自己的对数组的本地引用的地方,只有这样才检查它的两个成员。
      • @jameslarge 显然 - 因此我对类外部代码的评论无法访问数组本身。但是是否需要原子地检查这两个变量取决于操作的用例。 BTW Jean Logeart's answer基本上做同样的事情,除了两个整数被包装在一个类而不是一个数组中。
      猜你喜欢
      • 1970-01-01
      • 2018-03-02
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多