原文来自个人博客月泉的博客html
食用该篇文章,做者建议你最好已经提早了解过:JMM以及CPU缓存一致性协议还有相关的内存屏障的知识而且可以理解CPU的乱序执行,若是做者理解不当,欢迎指出。 若是本文对你有所帮助不妨给 博客的Github点个小星星,提问和文章有什么地方理解错了也能够直接在Github上提交issue
该篇文章讨论的议题:java
咱们从一个很常见的案例开始出发linux
public class Test { public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); demo.setName("demo-thread"); demo.start(); Thread.sleep(1000); demo.flag = false; demo.join(); System.out.println(demo.getName() + "线程执行完毕:" + demo.count); } static class Demo extends Thread{ boolean flag = true; int count = 0; @Override public void run(){ while (flag){ count ++; } } } }
这段代码在执行时,会发生没法中止下来的现象,显而易见,demo-thread
从主内存中读取出flag
的值为true
放在了工做内存中,而后while
去判断该值是否为true
若是为true
就会不断的循环,直至flag
的值为false
为止,而后在main
方法的线程中修改了demo
实例中的flag的值,但demo-thread
线程并无感知到flag
的值已经被main
线程修改成true
了,从而发生了没法中止的现象,简单点来讲这就是线程之间的可见性问题,简单的画个图加深下理解。c++
那么how to 解决呢?就是咱们这篇的主角volatile
(实际上解决的方式有多种,我这里是为了写这篇文章因此这样解决)git
public class Test { public static void main(String[] args) throws InterruptedException { ................... } static class Demo extends Thread{ volatile boolean flag = true; ........... } }
经过volatile
关键字来修饰该变量,使得该变量的修改是对其它线程可见的,同时volatile
还会禁止指令优化的重排序在修饰完volatile
后会在对该变量执行操做时插入内存屏障,在进入下一个议题以前先说说何为内存屏障。github
先说说处理器的内存屏障,再来讨论下JVM定义的内存屏障,首先理解内存屏障本质是什么,在本质上的内存屏障实际上就是一类同步屏障指令,加了屏障的地方,假如屏障前有读写操做以及屏障后也有读写操做,那么屏障前的读写操做必然必须先于屏障后的读写操做,屏障后的读写操做也必然必须后于屏障前的读写操做(这里若是理解了乱序执行应该很好理解)windows
处理器的内存屏障缓存
保障早于屏障以前的读操做以后再执行晚于屏障的读操做sass
保障早于屏障以前的写操做以后再执行晚于屏障的写操做多线程
保障早于屏障以前的读写操做以后再执行晚于屏障以后的读写操做
先来认识几个语义的指令,由于待会在JVM的实现中也会发现它的身影,这里简单阐述一下
acquire
: 屏障前的指令不会被排到屏障后去
release
: 屏障后的指令不会被排到屏障前去
fence
:屏障前的指令不会被排到屏障后去,屏障后的指令也不会排到屏障前去
JVM的内存屏障
LoadLoad
读屏障:例若有指令Load1
和Load2
那么假如插入屏障指令为Load1;LoadLoad;Load2
,在中间插入LoadLoad
屏障能够保障读操做不会进行乱序优化,即Load2
在执行时,Load1
的读操做应是执行完了的。
StoreStore
写屏障:例若有指令Store1
和Store2
那么假如插入屏障指令为Store1;StoreSotre;Store2
,在中间插入StoreStore
屏障能够保障写操做不会进行乱序优化,即Store2
在执行时,Store1
的写操做应是执行完了的,而且Sotre1
的写操做是对Store2
可见的,至于为何可见,我会在说完这四种屏障时给出答案。
LoadStore
读写屏障:例若有指令Load1
和Store2
那么假如插入屏障指令为Load1;LoadStore;Store2
,在中间插入LoadStore
屏障能够保障前面的读操做和后面的写操做不会被乱序优化,即Store2
执行时,Load1
应是执行完了的
StoreLoad
写读屏障:例若有指令Store1
和Load2
那么假如插入屏障的指令为Store1;StoreLoad;Load2
,在中间插入StoreLoad
屏障能够保障前面的写操做对后面的读操做不会被乱序优化,而且是可见的,即Load2
执行时,Store1
应是执行完了的,并其写操做对屏障后的读操做可见
若是不管是加了何种屏障,例如LoadStore
屏障即屏障前的读操做都会从主存中读取值,屏障后的指令会往主存写值,再例如StoreLoad
屏障即屏障前的写操做会往主存写值,从而对屏障后的读操做可见,固然仅仅是往主存写值也不能保障就是可见的,因此后面的读操做也是从主存中读值
So,如今是否是对volatile
很清晰了,也知道上面为何加了volatile
关键字后,变量是对其它线程可见的了吧?,从而使得咱们的flag
修改后可以在多线程环境下可以有效,以一个很是简单的例子,深刻的探讨下去。
public class Demo{ static volatile int i; public static void main(String[] args){ i = 1; } }
查看生成的字节码(部分片断)
static volatile int i; descriptor: I flags: ACC_STATIC, ACC_VOLATILE
能够看到在字节码文件上有一个ACC_VOLATILE
的标识符,接着打开JVM(我用的Hotspot)的代码来看看吧~
能够在JVM源码中看到有一个is_volatile
判断是不是volatile
访问限定符修饰的,而后再看字节码解释器的部分源码
注意有三个细节,首先会判断是否标识了volatile
,而后再判断类型,咱们这个是int
类型因此会调用release_int_field_put
,最后插入一道屏障storeload
,首先先看itos
的定义
顾名思义:表示栈顶缓存的int类型数据
接着看release_int_field_put
会发现它调用了OrderAccess::release_store
那么这个方法到底是干什么的呢?首先注意方法参数添加了volatile
关键字,这是c++的volatile
关键字和Java(java语法也有这个同名的关键字不是吗?)被该关键字所修饰的变量意味着易变的,再c++中修饰了这个关键字的变量每次使用时都会从变量对应的内存地址且编译器也不会对它进行优化。
那么os::atomic_copy64
又是什么呢?这里会针对不一样的系统,我这里只看Linux的
粗暴点,就是生成汇编代码去拷贝值吧?
接着咱们看
接着看OrderAccess::storeload
请告诉我这4个东西眼熟不眼熟?!?,这固然只是定义不一样系统下实现不同,咱们这里仍是看linux
的
看这个方法的实现,还有其它三种的实现是什么?本篇文章上面也对该语义进行了阐述吧?继续看linux
下的实现
FULL_MEM_BARRIER
是什么本文上面也说了吧
针对的环境不通实现也不一样,这里具体就再也不阐述
= = 写到这个小节的时候笔者是用的windows了,因此再补一下windows对fence实现的源码
看下在我机器上生成的汇编代码
[Disassembling for mach='amd64'] [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo' # parm0: rdx:rdx = '[Ljava/lang/String;' # [sp+0x40] (sp of caller) 0x00000000037e5320: mov dword ptr [rsp+0ffffffffffffa000h],eax 0x00000000037e5327: push rbp 0x00000000037e5328: sub rsp,30h 0x00000000037e532c: mov rsi,17cf2af8h ; {metadata(method data for {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo')} 0x00000000037e5336: mov edi,dword ptr [rsi+0dch] 0x00000000037e533c: add edi,8h 0x00000000037e533f: mov dword ptr [rsi+0dch],edi 0x00000000037e5345: mov rsi,17cf2a30h ; {metadata({method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo')} 0x00000000037e534f: and edi,0h 0x00000000037e5352: cmp edi,0h 0x00000000037e5355: je 37e537eh ;*iconst_1 ; - org.yuequan.thread.test.Demo::main@0 (line 6) 0x00000000037e535b: mov rsi,0d5b0dad0h ; {oop(a 'java/lang/Class' = 'org/yuequan/thread/test/Demo')} 0x00000000037e5365: mov edi,1h 0x00000000037e536a: mov dword ptr [rsi+68h],edi 0x00000000037e536d: lock add dword ptr [rsp],0h ;*putstatic i ; - org.yuequan.thread.test.Demo::main@1 (line 6) 0x00000000037e5372: add rsp,30h 0x00000000037e5376: pop rbp 0x00000000037e5377: test dword ptr [2f20100h],eax ; {poll_return} 0x00000000037e537d: ret 0x00000000037e537e: mov qword ptr [rsp+8h],rsi 0x00000000037e5383: mov qword ptr [rsp],0ffffffffffffffffh 0x00000000037e538b: call 37e20a0h ; OopMap{rdx=Oop off=112} ;*synchronization entry ; - org.yuequan.thread.test.Demo::main@-1 (line 6) ; {runtime_call} 0x00000000037e5390: jmp 37e535bh 0x00000000037e5392: nop 0x00000000037e5393: nop 0x00000000037e5394: mov rax,qword ptr [r15+2a8h] 0x00000000037e539b: mov r10,0h 0x00000000037e53a5: mov qword ptr [r15+2a8h],r10 0x00000000037e53ac: mov r10,0h 0x00000000037e53b6: mov qword ptr [r15+2b0h],r10 0x00000000037e53bd: add rsp,30h 0x00000000037e53c1: pop rbp 0x00000000037e53c2: jmp 374ece0h ; {runtime_call} 0x00000000037e53c7: hlt 0x00000000037e53c8: hlt 0x00000000037e53c9: hlt 0x00000000037e53ca: hlt 0x00000000037e53cb: hlt 0x00000000037e53cc: hlt 0x00000000037e53cd: hlt 0x00000000037e53ce: hlt 0x00000000037e53cf: hlt 0x00000000037e53d0: hlt 0x00000000037e53d1: hlt 0x00000000037e53d2: hlt 0x00000000037e53d3: hlt 0x00000000037e53d4: hlt 0x00000000037e53d5: hlt 0x00000000037e53d6: hlt 0x00000000037e53d7: hlt 0x00000000037e53d8: hlt 0x00000000037e53d9: hlt 0x00000000037e53da: hlt 0x00000000037e53db: hlt 0x00000000037e53dc: hlt 0x00000000037e53dd: hlt 0x00000000037e53de: hlt 0x00000000037e53df: hlt [Exception Handler] [Stub Code] 0x00000000037e53e0: call 3750aa0h ; {no_reloc} 0x00000000037e53e5: mov qword ptr [rsp+0ffffffffffffffd8h],rsp 0x00000000037e53ea: sub rsp,80h 0x00000000037e53f1: mov qword ptr [rsp+78h],rax 0x00000000037e53f6: mov qword ptr [rsp+70h],rcx 0x00000000037e53fb: mov qword ptr [rsp+68h],rdx 0x00000000037e5400: mov qword ptr [rsp+60h],rbx 0x00000000037e5405: mov qword ptr [rsp+50h],rbp 0x00000000037e540a: mov qword ptr [rsp+48h],rsi 0x00000000037e540f: mov qword ptr [rsp+40h],rdi 0x00000000037e5414: mov qword ptr [rsp+38h],r8 0x00000000037e5419: mov qword ptr [rsp+30h],r9 0x00000000037e541e: mov qword ptr [rsp+28h],r10 0x00000000037e5423: mov qword ptr [rsp+20h],r11 0x00000000037e5428: mov qword ptr [rsp+18h],r12 0x00000000037e542d: mov qword ptr [rsp+10h],r13 0x00000000037e5432: mov qword ptr [rsp+8h],r14 0x00000000037e5437: mov qword ptr [rsp],r15 0x00000000037e543b: mov rcx,6601c4e0h ; {external_word} 0x00000000037e5445: mov rdx,37e53e5h ; {internal_word} 0x00000000037e544f: mov r8,rsp 0x00000000037e5452: and rsp,0fffffffffffffff0h 0x00000000037e5456: call 65cd4510h ; {runtime_call} 0x00000000037e545b: hlt [Deopt Handler Code] 0x00000000037e545c: mov r10,37e545ch ; {section_word} 0x00000000037e5466: push r10 0x00000000037e5468: jmp 3727600h ; {runtime_call} 0x00000000037e546d: hlt 0x00000000037e546e: hlt 0x00000000037e546f: hlt Decoding compiled method 0x00000000037e4ed0: Code: Argument 0 is unknown.RIP: 0x37e5020 Code size: 0x00000110 [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo' # parm0: rdx:rdx = '[Ljava/lang/String;' # [sp+0x40] (sp of caller) 0x00000000037e5020: mov dword ptr [rsp+0ffffffffffffa000h],eax 0x00000000037e5027: push rbp 0x00000000037e5028: sub rsp,30h ;*iconst_1 ; - org.yuequan.thread.test.Demo::main@0 (line 6) 0x00000000037e502c: mov rsi,0d5b0dad0h ; {oop(a 'java/lang/Class' = 'org/yuequan/thread/test/Demo')} 0x00000000037e5036: mov edi,1h 0x00000000037e503b: mov dword ptr [rsi+68h],edi 0x00000000037e503e: lock add dword ptr [rsp],0h ;*putstatic i ; - org.yuequan.thread.test.Demo::main@1 (line 6) 0x00000000037e5043: add rsp,30h 0x00000000037e5047: pop rbp 0x00000000037e5048: test dword ptr [2f20100h],eax ; {poll_return} 0x00000000037e504e: ret 0x00000000037e504f: nop 0x00000000037e5050: nop 0x00000000037e5051: mov rax,qword ptr [r15+2a8h] 0x00000000037e5058: mov r10,0h 0x00000000037e5062: mov qword ptr [r15+2a8h],r10 0x00000000037e5069: mov r10,0h 0x00000000037e5073: mov qword ptr [r15+2b0h],r10 0x00000000037e507a: add rsp,30h 0x00000000037e507e: pop rbp 0x00000000037e507f: jmp 374ece0h ; {runtime_call} 0x00000000037e5084: hlt 0x00000000037e5085: hlt 0x00000000037e5086: hlt 0x00000000037e5087: hlt 0x00000000037e5088: hlt 0x00000000037e5089: hlt 0x00000000037e508a: hlt 0x00000000037e508b: hlt 0x00000000037e508c: hlt 0x00000000037e508d: hlt 0x00000000037e508e: hlt 0x00000000037e508f: hlt 0x00000000037e5090: hlt 0x00000000037e5091: hlt 0x00000000037e5092: hlt 0x00000000037e5093: hlt 0x00000000037e5094: hlt 0x00000000037e5095: hlt 0x00000000037e5096: hlt 0x00000000037e5097: hlt 0x00000000037e5098: hlt 0x00000000037e5099: hlt 0x00000000037e509a: hlt 0x00000000037e509b: hlt 0x00000000037e509c: hlt 0x00000000037e509d: hlt 0x00000000037e509e: hlt 0x00000000037e509f: hlt [Exception Handler] [Stub Code] 0x00000000037e50a0: call 3750aa0h ; {no_reloc} 0x00000000037e50a5: mov qword ptr [rsp+0ffffffffffffffd8h],rsp 0x00000000037e50aa: sub rsp,80h 0x00000000037e50b1: mov qword ptr [rsp+78h],rax 0x00000000037e50b6: mov qword ptr [rsp+70h],rcx 0x00000000037e50bb: mov qword ptr [rsp+68h],rdx 0x00000000037e50c0: mov qword ptr [rsp+60h],rbx 0x00000000037e50c5: mov qword ptr [rsp+50h],rbp 0x00000000037e50ca: mov qword ptr [rsp+48h],rsi 0x00000000037e50cf: mov qword ptr [rsp+40h],rdi 0x00000000037e50d4: mov qword ptr [rsp+38h],r8 0x00000000037e50d9: mov qword ptr [rsp+30h],r9 0x00000000037e50de: mov qword ptr [rsp+28h],r10 0x00000000037e50e3: mov qword ptr [rsp+20h],r11 0x00000000037e50e8: mov qword ptr [rsp+18h],r12 0x00000000037e50ed: mov qword ptr [rsp+10h],r13 0x00000000037e50f2: mov qword ptr [rsp+8h],r14 0x00000000037e50f7: mov qword ptr [rsp],r15 0x00000000037e50fb: mov rcx,6601c4e0h ; {external_word} 0x00000000037e5105: mov rdx,37e50a5h ; {internal_word} 0x00000000037e510f: mov r8,rsp 0x00000000037e5112: and rsp,0fffffffffffffff0h 0x00000000037e5116: call 65cd4510h ; {runtime_call} 0x00000000037e511b: hlt [Deopt Handler Code] 0x00000000037e511c: mov r10,37e511ch ; {section_word} 0x00000000037e5126: push r10 0x00000000037e5128: jmp 3727600h ; {runtime_call} 0x00000000037e512d: hlt 0x00000000037e512e: hlt 0x00000000037e512f: hlt
这么长肿么看?看关键部位就好拉
看到是使用的lock
指令吧?那么问题来了lock
指令是什么,我在这里肤浅的解释一下:CPU提供了在执行指令期间提供了总线加锁的手段,那么加了lock
的汇编生成机器码就使CPU在执行这条指令的时候会把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,从而保证这条指令执行的原子性
经过内存屏障来提醒编译器和CPU不对指令进行优化防止其优化乱序执行从而达到有序性,在屏障的先后都是经过主存读写达到线程之间的可见性。(O(∩_∩)O 我想不用再多解释了)
就好比在多线程中,多个线程对于一个实例变量的变量i
进行自增操做,例如i++
,这时候就产生了竞态条件致使,例如给i
变量加5000次,获得的结果多是5000获得的结果也多是少于5000,尽管你加了volatile
,这是为何呢?要知道volatile
仅是经过内存屏障的机制来保障
例如
load1;load2;store1;store2;StoreLoad;load3;store3.....
尽管你保证了可见性,但你不能保证该操做的原子性,所谓的原子性本质就是指令在执行期间不被打断,要么就是不执行,仔细想一想i++
是否是一个三步的复合操做:取值、相加、赋值,就好比:你在未赋值时别的线程执行时,别的线程也正在处以赋值状态,语言解释好麻烦看下列示例
i= 0 线程A 线程B 取值 0 取值 0 相加 1 相加 1 赋值 1 赋值 1
尽管你保障了可见性,但你并不能保证你拿到手上的值永远是最新值。