JUC中Atomic class之lazySet的一点疑惑

JUC中Atomic class之lazySet的一点疑惑

最近再次翻netty和disrupt的源码, 发现一些地方使用AtomicXXX.lazySet()/unsafe.putOrderedXXX系列, 以前一直没有注意lazySet这个方法, 仔细研究一下发现很有意思。我们拿AtomicReferenceFieldUpdater的set()和lazySet()作比较, 其他AtomicXXX类和这个类似。

public void set(T obj, V newValue) {
    // ...
    unsafe.putObjectVolatile(obj, offset, newValue);
}

public void lazySet(T obj, V newValue) {
    // ...
    unsafe.putOrderedObject(obj, offset, newValue);
}

1.首先set()是对volatile变量的一个写操作, 我们知道volatile的write为了保证对其他线程的可见性会追加以下两个Fence(内存屏障)
1)StoreStore // 在intel cpu中, 不存在[写写]重排序, 这个可以直接省略了
2)StoreLoad // 这个是所有内存屏障里最耗性能的
注: 内存屏障相关参考Doug Lea大大的cookbook (http://g.oswego.edu/dl/jmm/cookbook.html)

2.Doug Lea大大又说了, lazySet()省去了StoreLoad屏障, 只留下StoreStore
在这里 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
把最耗性能的StoreLoad拿掉, 性能必然会提高不少(虽然不能禁止写读的重排序了保证不了可见性, 但给其他应用场景提供了更好的选择, 比如上边连接中Doug Lea举例的场景)。

但是但是, 在好奇心驱使下我翻了下JDK的源码(unsafe.cpp):

// 这是unsafe.putObjectVolatile()
UNSAFE_ENTRY(void, Unsafe_SetObjectVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
    UnsafeWrapper("Unsafe_SetObjectVolatile");
    oop x = JNIHandles::resolve(x_h);
    oop p = JNIHandles::resolve(obj);
    void* addr = index_oop_from_field_offset_long(p, offset);
    OrderAccess::release();
    if (UseCompressedOops) {
        oop_store((narrowOop*)addr, x);
    } else {
        oop_store((oop*)addr, x);
    }
    OrderAccess::fence();
UNSAFE_END

// 这是unsafe.putOrderedObject()
UNSAFE_ENTRY(void, Unsafe_SetOrderedObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
    UnsafeWrapper("Unsafe_SetOrderedObject");
    oop x = JNIHandles::resolve(x_h);
    oop p = JNIHandles::resolve(obj);
    void* addr = index_oop_from_field_offset_long(p, offset);
    OrderAccess::release();
    if (UseCompressedOops) {
        oop_store((narrowOop*)addr, x);
    } else {
        oop_store((oop*)addr, x);
    }
    OrderAccess::fence();
UNSAFE_END

仔细看代码是不是有种被骗的感觉, 他喵的一毛一样啊. 难道是JIT做了手脚?生成汇编看看

生成assembly code需要hsdis插件

mac平台从这里下载 https://kenai.com/projects/base-hsdis/downloads/directory/gnu-versions

linux和windows可以从R大的[高级语言虚拟机圈子]下载 http://hllvm.group.iteye.com/

为了测试代码简单, 使用AtomicLong来测:

// set()
public class LazySetTest {
    private static final AtomicLong a = new AtomicLong();

    public static void main(String[] args) {
        for (int i = 0; i < 100000000; i++) {
            a.set(i);
        }
    }
}

// lazySet()
public class LazySetTest {
    private static final AtomicLong a = new AtomicLong();

    public static void main(String[] args) {
        for (int i = 0; i < 100000000; i++) {
            a.lazySet(i);
        }
    }
}

分别执行以下命令:

1.export LD_LIBRARY_PATH=~/hsdis插件路径/
2.javac LazySetTest.java && java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LazySetTest

// ------------------------------------------------------
// set()的assembly code片段:
0x000000010ccbfeb3: mov    %r10,0x10(%r9)
0x000000010ccbfeb7: lock addl $0x0,(%rsp)     ;*putfield value
                                            ; - java.util.concurrent.atomic.AtomicLong::set@2 (line 112)
                                            ; - LazySetTest::main@13 (line 13)
0x000000010ccbfebc: inc    %ebp               ;*iinc
                                            ; - LazySetTest::main@16 (line 12)
// ------------------------------------------------------
// lazySet()的assembly code片段:
0x0000000108766faf: mov    %r10,0x10(%rcx)    ;*invokevirtual putOrderedLong
                                            ; - java.util.concurrent.atomic.AtomicLong::lazySet@8 (line 122)
                                            ; - LazySetTest::main@13 (line 13)
0x0000000108766fb3: inc    %ebp               ;*iinc
                                            ; - LazySetTest::main@16 (line 12)

好吧, set()生成的assembly code多了一个lock前缀的指令

查询IA32手册可知道, lock addl $0x0,(%rsp)其实就是StoreLoad屏障了, 而lazySet()确实没生成StoreLoad屏障

这里JIT除了将方法内联, 相同代码生成不同指令是怎么做到的?

http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/6e9aa487055f/src/share/vm/classfile/vmSymbols.hpp

查看如上代码, 812行和868行分别有如下代码:

do_intrinsic(_putObjectVolatile,        sun_misc_Unsafe,        putObjectVolatile_name, putObject_signature,   F_RN)
do_intrinsic(_putOrderedObject,         sun_misc_Unsafe,        putOrderedObject_name, putOrderedObject_signature, F_RN)

putObjectVolatile与putOrderedObject都在vmSymbols.hpp的宏定义中,jvm会根据instrinsics id生成特定的指令集 putObjectVolatile与putOrderedObject生成的汇编指令不同估计是源于这里了, 继续往下看 hotspot/src/share/vm/opto/libaray_call.cpp这个类:
首先看如下两行代码:

case vmIntrinsics::_putObjectVolatile:        return inline_unsafe_access(!is_native_ptr,  is_store, T_OBJECT,   is_volatile);
case vmIntrinsics::_putOrderedObject:         return inline_unsafe_ordered_store(T_OBJECT);

再看inline_unsafe_access()和inline_unsafe_ordered_store(), 不贴出全部代码了, 只贴出重要的部分:

bool LibraryCallKit::inline_unsafe_ordered_store(BasicType type) {
  // This is another variant of inline_unsafe_access, differing in
  // that it always issues store-store ("release") barrier and ensures
  // store-atomicity (which only matters for "long").

  // ...
  if (type == T_OBJECT) // reference stores need a store barrier.
    store = store_oop_to_unknown(control(), base, adr, adr_type, val, type);
  else {
    store = store_to_memory(control(), adr, val, type, adr_type, require_atomic_access);
  }
  insert_mem_bar(Op_MemBarCPUOrder);
  return true;
}

---------------------------------------------------------------------------------------------------------

bool LibraryCallKit::inline_unsafe_access(bool is_native_ptr, bool is_store, BasicType type, bool is_volatile) {
  // ....

  if (is_volatile) {
    if (!is_store)
      insert_mem_bar(Op_MemBarAcquire);
    else
      insert_mem_bar(Op_MemBarVolatile);
  }

  if (need_mem_bar) insert_mem_bar(Op_MemBarCPUOrder);

  return true;
}

我们可以看到 inline_unsafe_access()方法中, 如果是is_volatile为true, 并且是store操作的话, 有这样的一句代码 insert_mem_bar(Op_MemBarVolatile), 而inline_unsafe_ordered_store没有插入这句代码

再继续看/hotspot/src/cpu/x86/vm/x86_64.ad的membar_volatile

instruct membar_volatile(rFlagsReg cr) %{
  match(MemBarVolatile);
  effect(KILL cr);
  ins_cost(400);

  format %{
    $$template
    if (os::is_MP()) {
      $$emit$$"lock addl [rsp + #0], 0\t! membar_volatile"
    } else {
      $$emit$$"MEMBAR-volatile ! (empty encoding)"
    }
  %}
  ins_encode %{
    __ membar(Assembler::StoreLoad);
  %}
  ins_pipe(pipe_slow);
%}

lock addl [rsp + #0], 0\t! membar_volatile指令原来来自这里

总结:
错过一些细节, 但在主流程上感觉是有一点点明白了, 有错误之处请指正

参考了以下资料:
1.http://g.oswego.edu/dl/jmm/cookbook.html
2.https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly
3.http://www.quora.com/How-does-AtomicLong-lazySet-work
4.http://bad-concurrency.blogspot.ru/2012/10/talk-from-jax-london.html 

时间: 2024-10-28 19:59:45

JUC中Atomic class之lazySet的一点疑惑的相关文章

ios-iOS的GCD中的一点疑惑,求大神详解

问题描述 iOS的GCD中的一点疑惑,求大神详解 dispatch_queue_create(""队列名"",0),这个队列名有什么作为,创几个线程这个队列名,不管一样不一样,作用都一样 解决方案 ios-GCD详解iOS-GCD多线程编程详解5IOS GCD的使用详解

ios-IOS项目中集成Phonegap有没有好一点的教程,博客,和链接?

问题描述 IOS项目中集成Phonegap有没有好一点的教程,博客,和链接? IOS项目中要集成一点Phonegap 的东西, 网上找了半天都是 用Phonegap来创建工程.到官网上面去 按照流程做完出现很多问题,没有可以参考的东西,有路过的大神,帮个忙给点建议! 解决方案 http://blog.csdn.net/xiazhi_125/article/details/38119869 http://blog.csdn.net/flamingsky007/article/details/392

ie兼容-关于IE条件语句的一点疑惑

问题描述 关于IE条件语句的一点疑惑 经常在网站头部看到类似于这样的IE条件注释: <!--[if IE 7 ]><html lang=""zh"" id=""ne_wrap"" class=""no-js ie7""><![endif]--> 虽然能够理解该注释语法:在浏览器版本为ie7时,应用该代码,非ie浏览器则只把其当做一条注释而忽略掉.但不太

关于gcc和g++编译c文件时的一点疑惑

问题描述 关于gcc和g++编译c文件时的一点疑惑 普遍观点,gcc把c文件当做c处理,把c++当做c++处理:而g++把二者都当做c++处理.这里的处理是指语法分析那一步. 但g++在编译过程中调用了gcc.我疑惑了,语法分析是编译的第二步吧,既然g++调用了gcc,那么它是怎么让gcc把c文件当做c++处理的. 还有一点,就是对于c文件编译出来的符号,使用gcc和g++到底一不一样.比如在c文件里定义int add(int a,int b):用gcc和g++最后编译出来的符号到底是什么?是c

java socket的一点疑惑

问题描述 今天看了看javasocket,发现了一点疑惑的问题,因为javasocket这块儿接触的比较少,所以求大神来帮忙.1.java在创建一个socketserver的时候可以传入一个ip地址,这个地址如果不是你本机的地址会出现什么情况?2.看代码注释CreatesasocketaddresswheretheIPaddressisthewildcardaddressandtheportnumberaspecifiedvalue.这里面wildcardaddress又是什么意思,中文解释为通

线程安全-委托的一点疑惑。为何此处仍然报线程不安全的异常【图】

问题描述 委托的一点疑惑.为何此处仍然报线程不安全的异常[图] 我对委托的理解是一个函数指针,将某个方法的指针交给一个委托,然后由委托根据指针找到创建方法的线程去安全的调用方法. 疑问1:异步委托是否新开线程了. 疑问2: 当用子线程更新主线程控件状态时考虑到线程安全性一般都这样做. 1 ***某线程的方法里*** 2 3 this.Invoke(New Action(()=>{ 4 lable1.Text="111"; 5 })); 6 7 ***某线程的方法里*** 在win

commonjs-关于CommonJS和AMD的一点疑惑

问题描述 关于CommonJS和AMD的一点疑惑 1.AMD规范是否是CommonJS的真子集?2.CommonJS是同步的还是异步的,还是两者都支持?3.RequireJS当用CommonJS格式书写define()方法时,module参数有何作用?exports呢?module.exprots与exprots又有何分别?可否混用?4.RequireJS的define()方法写成define( function( require exports module) { ... })时,匿名函数里的

内核-linux下一类字符设备使用同一个主设备号进行注册的一点疑惑?

问题描述 linux下一类字符设备使用同一个主设备号进行注册的一点疑惑? 最近在看framebuffer的内核源码: 在fbmem.c中有 register_chrdev(FB_MAJOR,"fb",&fb_fops) 这里的fb_fops应该就是上层访问的接口(write.read.mmap等操作). 在注册一个frambuffer设备的时候,会使用register_framebuffer,追踪可以看到 fb_info->dev = device_create(fb_c

web-数据双向绑定的一点疑惑

问题描述 数据双向绑定的一点疑惑 web的双据双向绑定到底是什么意思?我感觉只要dom正常,无论是接受用户数据,还是反馈用户显示,都能正常进行啊,这与数据双向绑定有什么关系?求解释!! 解决方案 双向绑定只是一种简化编程的风格.很多单页应用框架,比如angularjs或者knockoutjs之类,通过mvvm模式把数据和界面呈现分开. 一言以蔽之,这不是必须的概念,但是它可以简化你编程的思维. 解决方案二: 双向数据绑定其实就是将dom对象和JS对象绑定,两者双向互相影响.你可以不使用双向数据绑