Java Unsafe and volatile

起源

在阅读ConcurrentLinkedQueue中看到了很奇怪的用法,难以理解,如下,为什么会用到UNSAFE的这个putOrderedObject方法呢

/**
     * Tries to CAS head to p. If successful, repoint old head to itself
     * as sentinel for succ(), below.
     */
    final void updateHead(Node<E> h, Node<E> p) {
        if (h != p && casHead(h, p))
            h.lazySetNext(h);
    }
void lazySetNext(Node<E> val) {
            UNSAFE.putOrderedObject(this, nextOffset, val);
        }

探索

从源码的注释来看,依然看不懂为什么这么设计,这里只是提到了防止re order

/***
   * Sets the value of the integer field at the specified offset in the
   * supplied object to the given value.  This is an ordered or lazy
   * version of <code>putIntVolatile(Object,long,int)</code>, which
   * doesn't guarantee the immediate visibility of the change to other
   * threads.  It is only really useful where the integer field is
   * <code>volatile</code>, and is thus expected to change unexpectedly.
   * 设置obj对象中offset偏移地址对应的整型field的值为指定值。这是一个有序或者
   * 有延迟的<code>putIntVolatile</cdoe>方法,并且不保证值的改变被其他线程立
   * 即看到。只有在field被<code>volatile</code>修饰并且期望被意外修改的时候
   * 使用才有用。
   * 
   * @param obj the object containing the field to modify.
   *    包含需要修改field的对象
   * @param offset the offset of the integer field within <code>obj</code>.
   *       <code>obj</code>中整型field的偏移量
   * @param value the new value of the field.
   *      field将被设置的新值
   * @see #putIntVolatile(Object,long,int)
   */
  public native void putOrderedInt(Object obj, long offset, int value);

根源

https://stackoverflow.com/questions/7557156/atomicxxx-lazyset-in-terms-of-happens-before-edges

The lazySet operations do not create happens-before edges and are therefore not guaranteed to be immediately visible. This is a low-level optimization that has only a few use-cases, which are mostly in concurrent data structures.

The garbage collection example of nulling out linked list pointers has no user-visible side effects. The nulling is preferred so that if nodes in the list are in different generations, it doesn't force a more expensive collection to be performed to discard the link chain. The use of lazySet maintains hygenic semantics without incurring volatile write overhead.

Another example is the usage of volatile fields guarded by a lock, such as in ConcurrentHashMap. The fields are volatile to allow lock-free reads, but writes must be performed under a lock to ensure strict consistency. As the lock guarantees the happens-before edge on release, an optimization is to use lazySet when writing to the fields and flushing all of the updates when unlocking. This helps keep the critical section short by avoiding unnecessary stalls and bus traffic.

If you write a concurrent data structure then lazySet is a good trick to be aware of. Its a low-level optimization so its only worth considering when performance tuning.

根据这里的解释,原来这样

1 Concurrent

在并发环境下,没必要立即让所有持有head的引用立即失效,这样会导致一些问题,比如size函数刚刚拿到head,然后遍历的过程突然head的next失效了,导致size完全不对。所以应该保证一定短时间内的stale数据可用性

另外就是对于value的变化需要立即可见,对于有意义的数据的next链接,需要立即反应出来,所以这里只针对head摘除的情况做了这种处理。同理,对于不需要立即反应变化,但是同时又有场景需要立即反应变化的变量,必须设置为volatile,然后根据场景进行优化,putOrderedLong操作。


2 根据Stack Overflow上的解释,有锁可以保证内存可见性的情况下,对volatile变量,可以节省一些指令上的开销,毕竟store load是一个很重的操作。另外一个类似的例子就是如下,同样的putIntVolatile保证了volatile语义,上面的写操作可以节省不必要的内存屏障。

U.putOrderedObject(a, j, task);
U.putOrderedInt(q, QTOP, s + 1);
U.putIntVolatile(q, QLOCK, 0);

总结

这个用法比较底层,所以大部分文章里面都没有介绍这个用法。同时要理解这里的知识,本身需要理解JMM,内存屏障,happens before,unsafe,涉及的知识点很多。



Reference

happens before,这里引用下java doc里面的一部分描述,这里也是极少有人提到的部分内容,大部分文章都关注与基本的6条规则,这里同时也提到了Concurrent包对规则的扩展。

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html

Memory Consistency Properties
Chapter 17 of The Java™ Language Specification defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships. In particular:
Each action in a thread happens-before every action in that thread that comes later in the program's order.
An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.
A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.
A call to start on a thread happens-before any action in the started thread.
All actions in a thread happen-before any other thread successfully returns from a join on that thread.
The methods of all classes in java.util.concurrent and its subpackages extend these guarantees to higher-level synchronization. In particular:
Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.
Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService.
Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.
Actions prior to "releasing" synchronizer methods such as Lock.unlock, Semaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await on the same synchronizer object in another thread.
For each pair of threads that successfully exchange objects via an Exchanger, actions prior to the exchange() in each thread happen-before those subsequent to the corresponding exchange() in another thread.
Actions prior to calling CyclicBarrier.await and Phaser.awaitAdvance (as well as its variants) happen-before actions performed by the barrier action, and actions performed by the barrier action happen-before actions subsequent to a successful return from the corresponding await in other threads.
Since:
1.5

ConcurrentLinkedQueue源码分析,有点陈旧,但是有个点很重要,就是作者在设计offer的时候,减少了一半的volatile操作,所以head的移动规则复杂了

http://ifeve.com/concurrentlinkedqueue/


讲解了指令重排序,内存屏障,putOrderedInt与volatile性能对比,唯一没讲完善使用场景,缺少具体的案例

https://tech.meituan.com/java-memory-reordering.html


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值