【问题标题】:Does synchronized guarantee a thread will see the latest value of a non-volatile variable being modified by another thread?同步是否保证一个线程会看到另一个线程正在修改的非易失性变量的最新值?
【发布时间】:2014-03-28 07:31:02
【问题描述】:

这是一个简单的例子:

private long counter = 0;

// note this method is NOT synchronized
// this will be called by thread A
public void increment() { counter++; }

// note this method IS synchronized
// this will be called by thread B
public synchronized long value() { return counter; }

所以我只想为counter 获得一个好的值,而不是cpu 缓存中的卡住值,因为该变量是非易失性的。目标是不使计数器易失,因此它不会影响执行增量的线程 A,而只会影响线程 B,我不在乎,当它读取变量时。

为了记录,我打算在线程 A 已经完成时从线程 B 读取 counter 的值...

【问题讨论】:

    标签: java multithreading concurrency real-time java-memory-model


    【解决方案1】:

    不,线程 B 中的同步块不确保它会读取counter 的实际当前值。您将需要两个线程中的同步块来执行此操作。从实际的角度来看,您的代码确保运行线程 B 的处理器使其缓存无效并从主内存中读取 counter 的值,但它不能确保运行线程 A 的处理器将其当前值刷新到主内存,因此主内存中的值可能已过时。

    由于在两个线程中使用 volatile 变量比同步块便宜,因此使counter volatile 可能是正确的解决方案。这就是 volatile 变量的用途。

    编辑:如果线程 A 将在线程 B 读取最终值之前完成,您可以将线程 A 的整个执行包含在一个同步块中,或者在读取计数器之前让线程 B 加入线程 A,确保线程 A在读取计数器之前完成。这将导致在线程 A 执行结束时刷新一次缓存,这对性能的影响可以忽略不计。

    【讨论】:

    • increment() 方法在关键线程中被多次调用,因此我试图通过进行易失性写入而不是非易失性写入来不惜一切代价避免引入延迟。
    • 我理解你想要做什么。你有没有做过测试,看看它是否是一个真正的问题?取决于机器架构,如果线程 A 只是增加 volatile 值而不读取它,可能不会有太大影响;该值将不断从缓存推送到内存,但线程可能能够继续并行处理。
    • @JohnPristine 我刚刚阅读了您的编辑。如果您只在线程 A 完成后从线程 B 读取,那么您可以将线程 A 的整个执行包含在一个同步块中。这将导致在线程 A 执行开始时一次缓存失效,并在结束时导致一次缓存刷新,这对性能的影响可以忽略不计。
    • 好主意,沃伦。但是有点尴尬。我认为按照 axtavt 的建议做一个 join() 可能会更好,你觉得呢?
    【解决方案2】:

    long 赋值不保证是原子的,所以 B 不仅可以读取一个陈旧的值,它还可以读取一半写入的值。

    为了获得适当的可见性,您需要使 counter volatile。请注意,即使这样,从多个线程调用 increment n 次也可能不会将 counter 增加 n。

    您可以使用AtomicLong 来解决您的问题。

    【讨论】:

    • 谢谢,如果我只在线程 A 完成并完成后读取变量呢?
    • 仍然不能保证 - 例如 B 也可能永远读取 0。除非你开始 B A 完成之后。
    【解决方案3】:

    不,synchornized 只保证在同一个锁的同步块中所做的更改可见:

    synchornized(this) {
        counter++;
    }
    

    或在它们之前(由happens-before关系的传递性质定义):

    // Thread A
    counter++
    synchronized (this) {
        finished = true; 
    }
    
    // Thread B
    synchonized (this) {
        if (finished) {
            // you can read counter here
        }
    }
    

    但是请注意,如果您在确定线程 A 已完成(例如,使用 join())后阅读它,则保证counter 是可见的:

    threadA.join();
    // you can read counter here
    

    【讨论】:

      【解决方案4】:

      不。不能保证Thread B 总是给出最新的值。因为increment() 是非同步方法,而value() 是同步方法。
      自从

      当线程在对象的同步方法中时,所有其他希望执行此同步方法或对象的任何其他同步方法的线程都必须等待。

      此限制不适用于已经拥有锁并正在执行对象的同步方法的线程。这样的方法可以调用对象的其他同步方法而不会被阻塞。对象的非同步方法当然可以随时被任何线程调用。

      【讨论】:

        猜你喜欢
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多