598
阿裏雲
技術社區[雲棲]
JUC中Atomic class之lazySet的一點疑惑
最近再次翻netty和disrupt的源碼, 發現一些地方使用AtomicXXX.lazySet()/unsafe.putOrderedXXX係列, 以前一直沒有注意lazySet這個方法, 仔細研究一下發現很有意思。我們拿AtomicReferenceFieldUpdater的set()和lazySet()作比較, 其他AtomicXXX類和這個類似。
1 |
public void set(T obj, V newValue) {
|
3 |
unsafe.putObjectVolatile(obj, offset, newValue);
|
6 |
public void lazySet(T obj, V newValue) {
|
8 |
unsafe.putOrderedObject(obj, offset, newValue);
|
1.首先set()是對volatile變量的一個寫操作, 我們知道volatile的write為了保證對其他線程的可見性會追加以下兩個Fence(內存屏障)
1)StoreStore // 在intel cpu中, 不存在[寫寫]重排序, 這個可以直接省略了
2)StoreLoad // 這個是所有內存屏障裏最耗性能的
注: 內存屏障相關參考Doug Lea大大的cookbook (https://g.oswego.edu/dl/jmm/cookbook.html)
2.Doug Lea大大又說了, lazySet()省去了StoreLoad屏障, 隻留下StoreStore
在這裏 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
把最耗性能的StoreLoad拿掉, 性能必然會提高不少(雖然不能禁止寫讀的重排序了保證不了可見性, 但給其他應用場景提供了更好的選擇, 比如上邊連接中Doug Lea舉例的場景)。
但是但是, 在好奇心驅使下我翻了下JDK的源碼(unsafe.cpp):
01 |
// 這是unsafe.putObjectVolatile() |
02 |
UNSAFE_ENTRY( void , Unsafe_SetObjectVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
|
03 |
UnsafeWrapper( "Unsafe_SetObjectVolatile" );
|
04 |
oop x = JNIHandles::resolve(x_h);
|
05 |
oop p = JNIHandles::resolve(obj);
|
06 |
void * addr = index_oop_from_field_offset_long(p, offset);
|
07 |
OrderAccess::release();
|
08 |
if (UseCompressedOops) {
|
09 |
oop_store((narrowOop*)addr, x);
|
11 |
oop_store((oop*)addr, x);
|
16 |
// 這是unsafe.putOrderedObject() |
17 |
UNSAFE_ENTRY( void , Unsafe_SetOrderedObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
|
18 |
UnsafeWrapper( "Unsafe_SetOrderedObject" );
|
19 |
oop x = JNIHandles::resolve(x_h);
|
20 |
oop p = JNIHandles::resolve(obj);
|
21 |
void * addr = index_oop_from_field_offset_long(p, offset);
|
22 |
OrderAccess::release();
|
23 |
if (UseCompressedOops) {
|
24 |
oop_store((narrowOop*)addr, x);
|
26 |
oop_store((oop*)addr, x);
|
仔細看代碼是不是有種被騙的感覺, 他喵的一毛一樣啊. 難道是JIT做了手腳?生成匯編看看
生成assembly code需要hsdis插件
mac平台從這裏下載 https://kenai.com/projects/base-hsdis/downloads/directory/gnu-versions
linux和windows可以從R大的[高級語言虛擬機圈子]下載 https://hllvm.group.iteye.com/
為了測試代碼簡單, 使用AtomicLong來測:
02 |
public class LazySetTest {
|
03 |
private static final AtomicLong a = new AtomicLong();
|
05 |
public static void main(String[] args) {
|
06 |
for ( int i = 0 ; i < 100000000 ; i++) {
|
13 |
public class LazySetTest {
|
14 |
private static final AtomicLong a = new AtomicLong();
|
16 |
public static void main(String[] args) {
|
17 |
for ( int i = 0 ; i < 100000000 ; i++) {
|
分別執行以下命令:
01 |
1 .export LD_LIBRARY_PATH=~/hsdis插件路徑/
|
02 |
2 .javac LazySetTest.java && java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LazySetTest
|
04 |
// ------------------------------------------------------ |
05 |
// set()的assembly code片段: |
06 |
0x000000010ccbfeb3 : mov %r10, 0x10 (%r9)
|
07 |
0x000000010ccbfeb7 : lock addl $ 0x0 ,(%rsp) ;*putfield value
|
08 |
; - java.util.concurrent.atomic.AtomicLong::set @2 (line 112 )
|
09 |
; - LazySetTest::main @13 (line 13 )
|
10 |
0x000000010ccbfebc : inc %ebp ;*iinc
|
11 |
; - LazySetTest::main @16 (line 12 )
|
12 |
// ------------------------------------------------------ |
13 |
// lazySet()的assembly code片段: |
14 |
0x0000000108766faf : mov %r10, 0x10 (%rcx) ;*invokevirtual putOrderedLong
|
15 |
; - java.util.concurrent.atomic.AtomicLong::lazySet @8 (line 122 )
|
16 |
; - LazySetTest::main @13 (line 13 )
|
17 |
0x0000000108766fb3 : inc %ebp ;*iinc
|
18 |
; - LazySetTest::main @16 (line 12 )
|
好吧, set()生成的assembly code多了一個lock前綴的指令
查詢IA32手冊可知道, lock addl $0x0,(%rsp)其實就是StoreLoad屏障了, 而lazySet()確實沒生成StoreLoad屏障
這裏JIT除了將方法內聯, 相同代碼生成不同指令是怎麼做到的?
https://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/6e9aa487055f/src/share/vm/classfile/vmSymbols.hpp
查看如上代碼, 812行和868行分別有如下代碼:
1 |
do_intrinsic(_putObjectVolatile, sun_misc_Unsafe, putObjectVolatile_name, putObject_signature, F_RN) |
2 |
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這個類:
首先看如下兩行代碼:
1 |
case vmIntrinsics::_putObjectVolatile: return inline_unsafe_access(!is_native_ptr, is_store, T_OBJECT, is_volatile);
|
2 |
case vmIntrinsics::_putOrderedObject: return inline_unsafe_ordered_store(T_OBJECT);
|
再看inline_unsafe_access()和inline_unsafe_ordered_store(), 不貼出全部代碼了, 隻貼出重要的部分:
01 |
bool LibraryCallKit::inline_unsafe_ordered_store(BasicType type) { |
02 |
// This is another variant of inline_unsafe_access, differing in
|
03 |
// that it always issues store-store ("release") barrier and ensures
|
04 |
// store-atomicity (which only matters for "long").
|
07 |
if (type == T_OBJECT) // reference stores need a store barrier.
|
08 |
store = store_oop_to_unknown(control(), base, adr, adr_type, val, type);
|
10 |
store = store_to_memory(control(), adr, val, type, adr_type, require_atomic_access);
|
12 |
insert_mem_bar(Op_MemBarCPUOrder);
|
16 |
--------------------------------------------------------------------------------------------------------- |
18 |
bool LibraryCallKit::inline_unsafe_access(bool is_native_ptr, bool is_store, BasicType type, bool is_volatile) { |
23 |
insert_mem_bar(Op_MemBarAcquire);
|
25 |
insert_mem_bar(Op_MemBarVolatile);
|
28 |
if (need_mem_bar) insert_mem_bar(Op_MemBarCPUOrder);
|
我們可以看到 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
01 |
instruct membar_volatile(rFlagsReg cr) %{ |
02 |
match(MemBarVolatile);
|
09 |
$$emit$$ "lock addl [rsp + #0], 0\t! membar_volatile"
|
11 |
$$emit$$ "MEMBAR-volatile ! (empty encoding)"
|
15 |
__ membar(Assembler::StoreLoad);
|
lock addl [rsp + #0], 0\t! membar_volatile指令原來來自這裏
總結:
錯過一些細節, 但在主流程上感覺是有一點點明白了, 有錯誤之處請指正
參考了以下資料:
1.https://g.oswego.edu/dl/jmm/cookbook.html
2.https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly
3.https://www.quora.com/How-does-AtomicLong-lazySet-work
4.https://bad-concurrency.blogspot.ru/2012/10/talk-from-jax-london.html
最後更新:2017-05-22 15:34:08