今天给各位分享吾爱破解网站源码分享的知识,其中也会对安卓进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
volatile的内存语义
volatile变量的特性:
可见性:一个线程对volatile变量的修改可以被任意线程可见原子性:对于使用volatile变量的修改,不能是复合操作,如++v,这种操作不具有原子性
volatile写-读的内存语义:从jdk5(实现了jsr-133)开始,volatile变量的写-读实现类线程间的通信。
从内存语义角度来说,严格限制编译器和处理器对volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具有相同的效果:
volatile变量的写与锁的释放有相同的内存语义,JMM将当前线程对应的本地内存中的共享变量刷新到主内存;volatile变量的读与锁的获取有相同的内存语义,JMM会把当前线程对应的本地内存置为无效,之后从主内存中重新读取共享变量;
从上述描述中可以看出,不同线程对volatile变量的读和写操作通过主内存来进行通信。
volatile内存语义的实现:
为了实现volatile内存语义,在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
保守策略的JMM内存屏障插入策略如下:
在每个volatile写操作前面插入一个StoreStore屏障;在每个volatile写操作后面插入一个StoreLoad屏障;在每个volatile读操作后面插入一个LoadLoad屏障;在每个volatile读操作后面插入一个LoadStore屏障;
volatile仅对单个变量的写-读具有原子性,锁的互斥性可以确保一个临界区的代码执行具有原子性在功能上,锁比volatile更强大,但是volatile的伸缩性和执行性能上更有优势
使用hsdis工具(查看运行代码的汇编指令的工具),设置jvm参数如下:
-server-Xcomp-XX:+UnlockDiagnosticVMOptions-XX:+PrintAssembly-XX:CompileCommand=compileonly,*VolatileDemo.main
publicclassVolatileDemo{\n\npublicvolatilestaticbooleanstop=false;\n\npublicstaticvoidmain(String[]args)throwsInterruptedException{\nstop=true;\n}\n}
输出结果中会找到lock指令,在修改volatile修饰的成员变量时会多出
0x0000000002e2392a:movbyteptr[rsi+68h],dil\n0x0000000002e2392e:lockadddwordptr[rsp],0h;*putstaticstop\n;-com.gupaoedu.vip.VolatileDemo::main@1(line25)
final的内存语义
final域的重排序规则:
在构造函数内对一个final域的写入,之后把被构造函数对象的引用赋值给一个引用变量,这两个操作不能重排序;初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序;
写final域的重排序规则:
写final域的重排序规则禁止把final域的写重排序到构造函数之外,实现如下:
JMM禁止编译器把final域的写重排序到构造函数之外;编译器会在final域的写之后,构造函数return之前,插入StoreStore屏障(禁止处理器将final域的写重排序到构造函数之外)
作用:在对象引用被其他线程可见之前,该对象的final域已经被正确初始化,普通引用则不一定已被初始化。且在构造函数内部,不能让这个被构造对象的引用被其他线程可见(对象的引用不能“逸出”构造函数),示例如下。
publicclassFinalReferenceEscapeDemo{\nfinalinti;\nstaticFinalReferenceEscapeDemoobj;\n\npublicFinalReferenceEscapeDemo(){\nthis.i=1;\nobj=this;//this逸出\n}\n\npublicstaticvoidwrite(){\nnewFinalReferenceEscapeDemo();\n}\n\npublicstaticvoidread(){\nif(null!=obj){\ninta=obj.i;\n}\n}\n}
当有两个线程分别同时执行write和read方法时,a的值可能为0。因此,在遵守写final域的重排序规则的同时对象的引用也不能从构造函数中“逸出”。
读final域的重排序规则:
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作。编译器会在读final域操作前插入LoadLoad屏障。
初次读对象引用与初次读对象包含的final域,这两个操作之间存在间接的依赖关系。编译器遵守间接关系,不会重排序这两个操作。大多数的处理器(除了alpha处理器等)会遵守间接依赖,也不会重排序这两个操作。
作用:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用
final域为引用类型:
在构造函数内对一个final引用的对象成员的写入,与之后构造函数外把这个被构造的对象引用复制给一个引用变量,这两个操作不能重排序。
JSR-133增强了final语义:通过为final域增加写读重排序规则,只要对象被正确构造(被构造的对象的引用没有“逸出”构造函数),不需要使用同步就可以保证任意线程都能看到这个final域在构造函数中被初始化的值。
锁的内存语义
锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
当线程释放锁,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。当线程获取锁时,JMM会把该线程的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
对比锁的释放-获取的内存语义与volatile的写-读内存语义可以得出:锁释放与volatile写有相同语义;锁获取与volatile读有相同语义。
java中的CAS(compareAndSetInt(…)):如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值,这个操作具有volatile写-读的内存语义。
CAS的优势是无锁,减少线程切换,但是如果经常失败会影响性能,也可能存在ABA问题。java中current包源码实现的一个通用的模式:
声明共享变量为volatile;使用CAS的原子条件更新来实现线程间数据的同步;配合volatile的读写和CAS的内存语义实现线程间的通信。
concurrent包的实现示意图
对long/double类型变量的特殊规则
JMM中要求lock、unlock、read、assign、store、write,这些操作都是原子性,对于64位的数据类型如long、double,虚拟机有了long和double的非原子性协定,即将没有被volatile修饰的64位数据的读写操作分两次32位操作进行。但是现在大多数平台的虚拟机都把64位数据的读写作为原子操作来对待,所以也不用将用到的long和double变量都声明为volatile。
如果你还想了解更多这方面的信息,记得收藏关注本站。
