java 中volatile和lock原理分析
java中volatile和lock原理分析
volatile和lock是Java中用于线程协同同步的两种机制。
Volatile
volatile是Java中的一个关键字,它的作用有
- 保证变量的可见性
- 防止重排序
- 保证64位变量(long,double)的原子性读写
volatile在Java语言规范中规定的是
TheJavaprogramminglanguageallowsthreadstoaccesssharedvariables(§17.1).Asarule,toensure thatsharedvariablesareconsistentlyandreliablyupdated,athreadshouldensurethatit hasexclusiveuseofsuchvariablesbyobtainingalockthat,conventionally,enforcesmutual exclusionforthosesharedvariables. TheJavaprogramminglanguageprovidesasecondmechanism,volatilefields,thatismoreconvenient thanlockingforsomepurposes. Afieldmaybedeclaredvolatile,inwhichcasetheJavaMemoryModelensuresthatallthreads seeaconsistentvalueforthevariable. Itisacompile-timeerrorifafinalvariableisalsodeclaredvolatile.
Java内存模型中规定了volatile的happen-before效果,对volatile变量的写操作happen-before于后续的读。这样volatile变量能够确保一个线程的修改对其他线程可见。volatile因为不能保证原子性,所以不能在有约束或后验条件的场景下使用,如i++,常用的场景是stop变量保证系统停止对其他线程可见,double-checklock单例中防止重排序来保证安全发布等。
以下面这段代码为例
publicclassTestVolatile{
privatestaticvolatilebooleanstop=false;
publicstaticvoidmain(String[]args){
stop=true;
booleanb=stop;
}
}
stop字段声明为volatile类型后,编译后的字节码中其变量的access_flag中ACC_VOLATILE位置为1。
关键的字节码内容如下
publicstaticvoidmain(java.lang.String[]);
descriptor:([Ljava/lang/String;)V
flags:ACC_PUBLIC,ACC_STATIC
Code:
stack=1,locals=2,args_size=1
0:iconst_1
1:putstatic#2//Fieldstop:Z
4:getstatic#2//Fieldstop:Z
7:istore_1
8:return
LineNumberTable:
line14:0
line15:4
line16:8
LocalVariableTable:
StartLengthSlotNameSignature
090args[Ljava/lang/String;
811bZ
static{};
descriptor:()V
flags:ACC_STATIC
Code:
stack=1,locals=0,args_size=0
0:iconst_0
1:putstatic#2//Fieldstop:Z
4:return
LineNumberTable:
line11:0
}
通过hsdis查看虚拟机产生的汇编代码。
测试环境为javaversion“1.8.0_45”,MACOS10.12.1i386:x86-64
在执行参数上添加
-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*TestVolatile.main -XX:CompileCommand=compileonly,*TestVolatile.main
查看main方法的汇编指令结果
Decodingcompiledmethod0x000000010c732c50:
Code:
[Disassemblingformach='i386:x86-64']
[EntryPoint]
[VerifiedEntryPoint]
[Constants]
#{method}{0x000000012422a2c8}'main''([Ljava/lang/String;)V'in'com/concurrent/volatiles/TestVolatile'
#parm0:rsi:rsi='[Ljava/lang/String;'
#[sp+0x40](spofcaller)
0x000000010c732da0:mov%eax,-0x14000(%rsp)
0x000000010c732da7:push%rbp
0x000000010c732da8:sub$0x30,%rsp
0x000000010c732dac:movabs$0x12422a448,%rdi;{metadata(methoddatafor{method}{0x000000012422a2c8}'main''([Ljava/lang/String;)V'in'com/concurrent/volatiles/TestVolatile')}
0x000000010c732db6:mov0xdc(%rdi),%ebx
0x000000010c732dbc:add$0x8,%ebx
0x000000010c732dbf:mov%ebx,0xdc(%rdi)
0x000000010c732dc5:movabs$0x12422a2c8,%rdi;{metadata({method}{0x000000012422a2c8}'main''([Ljava/lang/String;)V'in'com/concurrent/volatiles/TestVolatile')}
0x000000010c732dcf:and$0x0,%ebx
0x000000010c732dd2:cmp$0x0,%ebx
0x000000010c732dd5:je0x000000010c732e03;*iconst_1
;-com.concurrent.volatiles.TestVolatile::main@0(line14)
0x000000010c732ddb:movabs$0x76adce798,%rsi;{oop(a'java/lang/Class'='com/concurrent/volatiles/TestVolatile')}
0x000000010c732de5:mov$0x1,%edi
0x000000010c732dea:mov%dil,0x68(%rsi)
0x000000010c732dee:lockaddl$0x0,(%rsp);*putstaticstop
;-com.concurrent.volatiles.TestVolatile::main@1(line14)
0x000000010c732df3:movsbl0x68(%rsi),%esi;*getstaticstop
;-com.concurrent.volatiles.TestVolatile::main@4(line15)
0x000000010c732df7:add$0x30,%rsp
0x000000010c732dfb:pop%rbp
0x000000010c732dfc:test%eax,-0x3adbd02(%rip)#0x0000000108c57100
;{poll_return}
0x000000010c732e02:retq
0x000000010c732e03:mov%rdi,0x8(%rsp)
0x000000010c732e08:movq$0xffffffffffffffff,(%rsp)
0x000000010c732e10:callq0x000000010c7267e0;OopMap{rsi=Oopoff=117}
;*synchronizationentry
;-com.concurrent.volatiles.TestVolatile::main@-1(line14)
;{runtime_call}
0x000000010c732e15:jmp0x000000010c732ddb
0x000000010c732e17:nop
0x000000010c732e18:nop
0x000000010c732e19:mov0x2a8(%r15),%rax
0x000000010c732e20:movabs$0x0,%r10
0x000000010c732e2a:mov%r10,0x2a8(%r15)
0x000000010c732e31:movabs$0x0,%r10
0x000000010c732e3b:mov%r10,0x2b0(%r15)
0x000000010c732e42:add$0x30,%rsp
0x000000010c732e46:pop%rbp
0x000000010c732e47:jmpq0x000000010c6940e0;{runtime_call}
[ExceptionHandler]
可以看到在mov%dil,0x68(%rsi)给stop赋值后增加了lockaddl$0x0,(%rsp)
IA32中对lock的说明是
TheLOCK#signalisassertedduringexecutionoftheinstructionfollowing thelockprefix.Thissignalcanbeusedinamultiprocessorsystemtoensure exclusiveuseofsharedmemorywhileLOCK#isasserted
lock用于在多处理器中执行指令时对共享内存的独占使用。它的副作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。
Lock
Java中提供的锁的关键字是synchronized,可以加在方法块上,也可以加在方法声明中。
synchronized关键字起到的作用是设置一个独占访问临界区,在进入这个临界区前要先获取对应的监视器锁,任何Java对象都可以成为监视器锁,声明在静态方法上时监视器锁是当前类的Class对象,实例方法上是当前实例。
synchronized提供了原子性、可见性和防止重排序的保证。
JMM中定义监视器锁的释放操作happen-before与后续的同一个监视器锁获取操作。再结合程序顺序规则就可以形成内存传递可见性保证。
下面以一段代码查看各个层次的实现
publicclassTestSynchronize{
privateintcount;
privatevoidinc(){
synchronized(this){
count++;
}
}
publicstaticvoidmain(String[]args){
newTestSynchronize().inc();
}
}
编译后inc方法的字节码为
privatevoidinc(); descriptor:()V flags:ACC_PRIVATE Code: stack=3,locals=3,args_size=1 0:aload_0 1:dup 2:astore_1 3:monitorenter 4:aload_0 5:dup 6:getfield#2//Fieldcount:I 9:iconst_1 10:iadd 11:putfield#2//Fieldcount:I 14:aload_1 15:monitorexit 16:goto24 19:astore_2 20:aload_1 21:monitorexit 22:aload_2 23:athrow 24:return Exceptiontable: fromtotargettype 41619any 192219any LineNumberTable: line14:0 line15:4
在synchronized代码块前后增加的monitorenter和monitorexist两个JVM字节码指令,指令的参数是this引用。
hotspot中对于monitor_enter和monitor_exit的处理是
voidLIRGenerator::monitor_enter(LIR_Oprobject,LIR_Oprlock,LIR_Oprhdr,LIR_Oprscratch,intmonitor_no,CodeEmitInfo*info_for_exception,CodeEmitInfo*info){
if(!GenerateSynchronizationCode)return;
//forslowpath,usedebuginfoforstateaftersuccessfullocking
CodeStub*slow_path=newMonitorEnterStub(object,lock,info);
__load_stack_address_monitor(monitor_no,lock);
//forhandlingNullPointerException,usedebuginforepresentingjustthelockstackbeforethismonitorenter
__lock_object(hdr,object,lock,scratch,slow_path,info_for_exception);
}
voidLIRGenerator::monitor_exit(LIR_Oprobject,LIR_Oprlock,LIR_Oprnew_hdr,LIR_Oprscratch,intmonitor_no){
if(!GenerateSynchronizationCode)return;
//setupregisters
LIR_Oprhdr=lock;
lock=new_hdr;
CodeStub*slow_path=newMonitorExitStub(lock,UseFastLocking,monitor_no);
__load_stack_address_monitor(monitor_no,lock);
__unlock_object(hdr,object,lock,scratch,slow_path);
}
inc方法在本机上输出的汇编代码为
Decodingcompiledmethod0x0000000115be3e50:
Code:
[EntryPoint]
[Constants]
#{method}{0x0000000113082328}'inc''()V'in'com/concurrent/lock/TestSynchronize'
#[sp+0x50](spofcaller)
0x0000000115be3fc0:mov0x8(%rsi),%r10d
0x0000000115be3fc4:shl$0x3,%r10
0x0000000115be3fc8:cmp%rax,%r10
0x0000000115be3fcb:jne0x0000000115b1de20;{runtime_call}
0x0000000115be3fd1:data32data32nopw0x0(%rax,%rax,1)
0x0000000115be3fdc:data32data32xchg%ax,%ax
[VerifiedEntryPoint]
0x0000000115be3fe0:mov%eax,-0x14000(%rsp)
0x0000000115be3fe7:push%rbp
0x0000000115be3fe8:sub$0x40,%rsp
0x0000000115be3fec:movabs$0x113082848,%rax;{metadata(methoddatafor{method}{0x0000000113082328}'inc''()V'in'com/concurrent/lock/TestSynchronize')}
0x0000000115be3ff6:mov0xdc(%rax),%edi
0x0000000115be3ffc:add$0x8,%edi
0x0000000115be3fff:mov%edi,0xdc(%rax)
0x0000000115be4005:movabs$0x113082328,%rax;{metadata({method}{0x0000000113082328}'inc''()V'in'com/concurrent/lock/TestSynchronize')}
0x0000000115be400f:and$0x0,%edi
0x0000000115be4012:cmp$0x0,%edi
0x0000000115be4015:je0x0000000115be418d;*aload_0
;-com.concurrent.lock.TestSynchronize::inc@0(line14)
0x0000000115be401b:lea0x20(%rsp),%rdi
0x0000000115be4020:mov%rsi,0x8(%rdi)
0x0000000115be4024:mov(%rsi),%rax
0x0000000115be4027:mov%rax,%rbx
0x0000000115be402a:and$0x7,%rbx
0x0000000115be402e:cmp$0x5,%rbx
0x0000000115be4032:jne0x0000000115be40b9
0x0000000115be4038:mov0x8(%rsi),%ebx
0x0000000115be403b:shl$0x3,%rbx
0x0000000115be403f:mov0xa8(%rbx),%rbx
0x0000000115be4046:or%r15,%rbx
0x0000000115be4049:xor%rax,%rbx
0x0000000115be404c:and$0xffffffffffffff87,%rbx
0x0000000115be4050:je0x0000000115be40e1
0x0000000115be4056:test$0x7,%rbx
0x0000000115be405d:jne0x0000000115be40a6
0x0000000115be405f:test$0x300,%rbx
0x0000000115be4066:jne0x0000000115be4085
0x0000000115be4068:and$0x37f,%rax
0x0000000115be406f:mov%rax,%rbx
0x0000000115be4072:or%r15,%rbx
0x0000000115be4075:lockcmpxchg%rbx,(%rsi)
0x0000000115be407a:jne0x0000000115be41a4
0x0000000115be4080:jmpq0x0000000115be40e1
0x0000000115be4085:mov0x8(%rsi),%ebx
0x0000000115be4088:shl$0x3,%rbx
0x0000000115be408c:mov0xa8(%rbx),%rbx
0x0000000115be4093:or%r15,%rbx
0x0000000115be4096:lockcmpxchg%rbx,(%rsi)
0x0000000115be409b:jne0x0000000115be41a4
0x0000000115be40a1:jmpq0x0000000115be40e1
0x0000000115be40a6:mov0x8(%rsi),%ebx
0x0000000115be40a9:shl$0x3,%rbx
0x0000000115be40ad:mov0xa8(%rbx),%rbx
0x0000000115be40b4:lockcmpxchg%rbx,(%rsi)
0x0000000115be40b9:mov(%rsi),%rax
0x0000000115be40bc:or$0x1,%rax
0x0000000115be40c0:mov%rax,(%rdi)
0x0000000115be40c3:lockcmpxchg%rdi,(%rsi)
0x0000000115be40c8:je0x0000000115be40e1
0x0000000115be40ce:sub%rsp,%rax
0x0000000115be40d1:and$0xfffffffffffff007,%rax
0x0000000115be40d8:mov%rax,(%rdi)
0x0000000115be40db:jne0x0000000115be41a4;*monitorenter
;-com.concurrent.lock.TestSynchronize::inc@3(line14)
0x0000000115be40e1:mov0xc(%rsi),%eax;*getfieldcount
;-com.concurrent.lock.TestSynchronize::inc@6(line15)
0x0000000115be40e4:inc%eax
0x0000000115be40e6:mov%eax,0xc(%rsi);*putfieldcount
;-com.concurrent.lock.TestSynchronize::inc@11(line15)
0x0000000115be40e9:lea0x20(%rsp),%rax
0x0000000115be40ee:mov0x8(%rax),%rdi
0x0000000115be40f2:mov(%rdi),%rsi
0x0000000115be40f5:and$0x7,%rsi
0x0000000115be40f9:cmp$0x5,%rsi
0x0000000115be40fd:je0x0000000115be411a
0x0000000115be4103:mov(%rax),%rsi
0x0000000115be4106:test%rsi,%rsi
0x0000000115be4109:je0x0000000115be411a
0x0000000115be410f:lockcmpxchg%rsi,(%rdi)
0x0000000115be4114:jne0x0000000115be41b7;*monitorexit
;-com.concurrent.lock.TestSynchronize::inc@15(line16)
0x0000000115be411a:movabs$0x113082848,%rax;{metadata(methoddatafor{method}{0x0000000113082328}'inc''()V'in'com/concurrent/lock/TestSynchronize')}
0x0000000115be4124:incl0x108(%rax);*goto
;-com.concurrent.lock.TestSynchronize::inc@16(line16)
0x0000000115be412a:add$0x40,%rsp
0x0000000115be412e:pop%rbp
0x0000000115be412f:test%eax,-0x684e035(%rip)#0x000000010f396100
;{poll_return}
0x0000000115be4135:retq;*return
;-com.concurrent.lock.TestSynchronize::inc@24(line17)
0x0000000115be4136:mov0x2a8(%r15),%rax
0x0000000115be413d:xor%r10,%r10
0x0000000115be4140:mov%r10,0x2a8(%r15)
0x0000000115be4147:xor%r10,%r10
0x0000000115be414a:mov%r10,0x2b0(%r15)
0x0000000115be4151:mov%rax,%rsi
0x0000000115be4154:lea0x20(%rsp),%rax
0x0000000115be4159:mov0x8(%rax),%rbx
0x0000000115be415d:mov(%rbx),%rdi
0x0000000115be4160:and$0x7,%rdi
0x0000000115be4164:cmp$0x5,%rdi
0x0000000115be4168:je0x0000000115be4185
0x0000000115be416e:mov(%rax),%rdi
0x0000000115be4171:test%rdi,%rdi
0x0000000115be4174:je0x0000000115be4185
0x0000000115be417a:lockcmpxchg%rdi,(%rbx)
0x0000000115be417f:jne0x0000000115be41ca;*monitorexit
;-com.concurrent.lock.TestSynchronize::inc@21(line16)
0x0000000115be4185:mov%rsi,%rax
0x0000000115be4188:jmpq0x0000000115be4205
0x0000000115be418d:mov%rax,0x8(%rsp)
0x0000000115be4192:movq$0xffffffffffffffff,(%rsp)
0x0000000115be419a:callq0x0000000115bd5be0;OopMap{rsi=Oopoff=479}
;*synchronizationentry
;-com.concurrent.lock.TestSynchronize::inc@-1(line14)
;{runtime_call}
0x0000000115be419f:jmpq0x0000000115be401b
0x0000000115be41a4:mov%rsi,0x8(%rsp)
0x0000000115be41a9:mov%rdi,(%rsp)
0x0000000115be41ad:callq0x0000000115bd4060;OopMap{rsi=Oop[40]=Oopoff=498}
;*monitorenter
;-com.concurrent.lock.TestSynchronize::inc@3(line14)
;{runtime_call}
0x0000000115be41b2:jmpq0x0000000115be40e1
0x0000000115be41b7:lea0x20(%rsp),%rax
0x0000000115be41bc:mov%rax,(%rsp)
0x0000000115be41c0:callq0x0000000115bd4420;{runtime_call}
0x0000000115be41c5:jmpq0x0000000115be411a
0x0000000115be41ca:lea0x20(%rsp),%rax
0x0000000115be41cf:mov%rax,(%rsp)
0x0000000115be41d3:callq0x0000000115bd4420;{runtime_call}
0x0000000115be41d8:jmp0x0000000115be4185
0x0000000115be41da:nop
0x0000000115be41db:nop
0x0000000115be41dc:mov0x2a8(%r15),%rax
0x0000000115be41e3:movabs$0x0,%r10
0x0000000115be41ed:mov%r10,0x2a8(%r15)
0x0000000115be41f4:movabs$0x0,%r10
0x0000000115be41fe:mov%r10,0x2b0(%r15)
0x0000000115be4205:add$0x40,%rsp
0x0000000115be4209:pop%rbp
0x0000000115be420a:jmpq0x0000000115b440e0;{runtime_call}
[ExceptionHandler]
其中lockcmpxchg为CompareAndExchange
CMPXCHGcomparesitsdestination(first)operandtothe valueinAL,AXorEAX(dependingonthesizeoftheinstruction). Iftheyareequal,itcopiesitssource(second)operandintothedestination andsetsthezeroflag.Otherwise,itclearsthezeroflagandleavesthedestinationalone.
CMPXCHGisintendedtobeusedforatomicoperationsinmultitaskingormultiprocessorenvironments.Tosafelyupdateavalueinsharedmemory,forexample,youmightloadthevalueintoEAX,loadtheupdatedvalueintoEBX,andthenexecutetheinstructionlockcmpxchg[value],ebx.Ifvaluehasnotchangedsincebeingloaded,itisupdatedwithyourdesirednewvalue,andthezeroflagissettoletyouknowithasworked.(TheLOCKprefixpreventsanotherprocessordoinganythinginthemiddleofthisoperation:itguaranteesatomicity.)However,ifanotherprocessorhasmodifiedthevalueinbetweenyourloadandyourattemptedstore,thestoredoesnothappen,andyouarenotifiedofthefailurebyaclearedzeroflag,soyoucangoroundandtryagain.
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!