Hibernate 源代码中的易失性屏障将 "syncs state with other threads"。如何?

volatile barrier in Hibernate source code would "syncs state with other threads". How?

我今天在研究 hibernate-jpa 的源代码时偶然发现了以下代码片段(您也可以找到 here):

private static class PersistenceProviderResolverPerClassLoader implements PersistenceProviderResolver {

    //FIXME use a ConcurrentHashMap with weak entry
    private final WeakHashMap<ClassLoader, PersistenceProviderResolver> resolvers =
            new WeakHashMap<ClassLoader, PersistenceProviderResolver>();
    private volatile short barrier = 1;

    /**
     * {@inheritDoc}
     */
    public List<PersistenceProvider> getPersistenceProviders() {
        ClassLoader cl = getContextualClassLoader();
        if ( barrier == 1 ) {} //read barrier syncs state with other threads
        PersistenceProviderResolver currentResolver = resolvers.get( cl );
        if ( currentResolver == null ) {
            currentResolver = new CachingPersistenceProviderResolver( cl );
            resolvers.put( cl, currentResolver );
            barrier = 1;
        }
        return currentResolver.getPersistenceProviders();
    }

那个奇怪的说法if ( barrier == 1 ) {} //read barrier syncs state with other threads让我很不安。我花时间深入研究了 volatile 关键字规范。

简单地说,在我的理解中,它确保对相应变量的任何 READWRITE 操作将始终直接在通常存储值的内存中执行.它专门防止通过缓存或注册器进行访问,这些缓存或注册器持有值的副本,并且不一定知道该值是否已更改或正在被另一个核心上的并发线程修改。

因此,它会导致性能下降,因为每次访问都意味着要一直进入内存,而不是使用通常的(流水线?)快捷方式。但它也确保无论何时线程读取变量,它都将始终是最新的。

我提供这些详细信息是为了让您了解我对关键字的理解。但是现在,当我重新阅读代码时,我告诉自己“好吧,我们通过确保始终为 1 的值始终为 1(并将其设置为 1)来减慢执行速度。这有什么帮助?"

谁能解释一下?

这样做是为了通过建立 happens before 关系 (https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/happens-before.html) 将对 resolvers 的更新映射到其他线程。

在单线程中,以下指令发生在关系之前

resolvers.put( cl, currentResolver );
barrier = 1;

但是要使 resolvers 中的更改对其他线程可见,我们需要从 volatile 变量 barrier 中读取值,因为对同一个 volatile 变量的写入和后续读取建立发生在关系之前(这也是传递)。所以基本上这是总体结果:

  1. 更新resolvers
  2. 写入易失性 barrier
  3. 从 volatile barrier 读取以使第 1 步中的更新对从 barrier
  4. 读取值的线程可见

易失性变量 - 是 Java 中的轻量级同步形式。

声明一个字段volatile将产生以下效果:

  • 编译器不会重新排序操作
  • 变量不会在寄存器中兑现
  • 64 位数据结构上的操作将作为原子操作执行
  • 会影响其他变量的可见性同步

引自 Brian Goetz 的并发实践:

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

好的,保留 1 而不是将 resolvers 声明为 volatile WeakHashMap 有什么意义?

此安全发布保证仅适用于原始字段和对象引用。出于此可见性保证的目的,实际成员是对象引用; volatile 对象引用所引用的对象超出了安全发布保证的范围。因此,将对象引用声明为易失性不足以保证将对引用对象成员的更改发布到其他线程。线程可能无法观察到最近从另一个线程写入此类对象引用的成员字段。

此外,当引用对象可变且缺乏线程安全时,其他线程可能会看到部分构造的对象或处于不一致状态的对象。

Map 对象的实例是可变的,因为其 put() 方法。

get()put() 的交错调用可能会导致从 Map 对象检索内部不一致的值,因为 put() 修改了它的状态。声明对象引用 volatile 不足以消除此数据竞争。

由于volatile变量建立了happens-before关系,当一个线程有更新时,正好可以通知其他访问barrier.

From a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block.

你理解volatile错了

it ensures that any READ or WRITE operation on the corresponding variable will allways be performed directly in the memory at the place the value is usually stored. It specifically prevents accesses through caches or registrars that hold a copy of the value and are not necessarily aware if the value has changed or is being modified by a concurrent thread on another core.

你说的是实现,而实现可能因 jvm 而异。


volatile很像某种规范或规则,它可以gurantee

Write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable. This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

Using simple atomic variable access is more efficient than accessing these variables through synchronized code, but requires more care by the programmer to avoid memory consistency errors. Whether the extra effort is worthwhile depends on the size and complexity of the application.


在这种情况下,volatile不用于保证barrier == 1:

if ( barrier == 1 ) {} //read
PersistenceProviderResolver currentResolver = resolvers.get( cl );
if ( currentResolver == null ) {
    currentResolver = new CachingPersistenceProviderResolver( cl );
    resolvers.put( cl, currentResolver );
    barrier = 1; //write
}

用于保证读写之间的副作用对其他线程可见。

没有它,如果你在 Thread1 的 resolvers 中放一些东西,Thread2 可能不会注意到它。

有了它,如果 Thread2 在 Thread1 写入后读取 barrier,则 Thread2 保证看到此 put 操作。


而且,还有很多其他的同步机制,比如:

  • synchronized 关键词
  • ReentrantLock
  • AtomicInteger
  • .....

通常,他们也可以在不同线程之间建立这种happens-before关系。