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.
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!