一、JAVA内存模型简介
JAVA Merory Model描述了JAVA程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
所有的变量都保存在主内存中,但是每个线程都有自己的独立工作内存,保存该线程使用到的变量的一个副本。
两条规定
1.线程对共享变量的操作只能在独立的工作内存中进行,不能在主内存中直接读写;
2.不同线程之间无法直接访问其他线程的工作内存中的变量,需要通过主内存完成。
实现共享变量的可见性,必须保证两点
1.线程修改后的共享变量能及时从工作内存刷新到主内存中;
2.其他线程能及时把共享变量的最新值从主内存中更新到工作内存中。
JAVA从语言层面支持的可见性实现方式
synchronized; volatile
二、synchronized实现可见性
synchronized除了使代码同时只能被一个线程执行的功能,还有一个功能就是实现可见性。
关于synchronized的两条规定
1.线程解锁前,必须把共享变量的最新值刷到主内存去;
2.线程加锁时,清空工作内存的共享变量值,从主内存中重新读取。
这两条规定保证了线程在解锁前对共享变量的操作在下次加锁前对其他线程可见。
synchronized实现可见性的过程
1.获得互斥锁
2.清空工作内存
3.从主内存中拷贝共享变量的最新值到工作内存
4.执行代码
5.把修改后的变量值更新到主内存
6.释放锁
as-if-serial与重排序
重排序:
代码的执行顺序和书写顺序不相同,指令重排序是编译器或处理器为了提高性能而做的优化。
分为 编译器优化、处理器指令级并行重排序、内存系统的重排序(对读写缓存的优化,如上所说的那些)
as-if-serial:无论如何重排序,程序执行的结果与代码顺序执行的结果相一致。(这并不是说就不进行重排序了!)
Java在单线程下是保证as-if-serial的。
导致共享变量在线程间不可见的原因及解决方案
1.线程交叉执行;
synchronized的原子性解决这个问题
2.重排序+线程交叉执行(甚至if() 与 {}中都可能被重排序,控制依赖关系并不是重排序的约束,只有数据依赖关系才是)
synchronized的原子性解决这个问题
3.共享变量更新后的值没有在主内存与工作内存之间及时更新
synchronized的两条规定解决这个问题
三、volatile实现可见性
volatile关键字可以保证volatile变量的可见性,但是不能保证volatile变量复合操作的原子性。
volatile如何实现
通过加入内存屏障和禁止指令重排序来实现:
对volatile变量执行写操作时,会在写操作后加一条store屏障指令,会把CPU写缓存的缓存强制刷新到主内存,还能防止处理器把volatile前的变量重排序到volatile之后。
对volatile变量执行读操作时,会在读操作前加一条load屏障指令。在读取时强制从主内存中读取。
通俗的说,就是volatile变量每次被线程访问时,都强制从主内存中重读该变量的值,而不是从工作内存的缓存中;当该值发生变化时,强制线程把最新的值刷新到主内存中。
不能保证volatile变量复合操作的原子性
比如 :
private volatile int a=0;
a++; //不是原子操作:分三步, 读取a的值,a+1;写回a的值
但是下面的是原子操作:
synchronized(this){
a++;
}
除了synchronized之外,还可以用Lock:
Lock lock= new ReentrantLock(); //可重入锁
然后在后面:
lock.lock();
try{
a++;
}finally{
lock.unlock();
}
volatile的适用场景
1.对变量的写入操作不依赖当前值
不满足: num++; num=num*5 ...
满足: 布尔变量等等
2.该变量没有包含在具有其他变量的不变式中
即程序中有多个volatile变量时,各自要独立。如a,b; 如果有a<b这样的式子,就不满足。
所以volatie的适用范围不如synchronized大。
四、synchronized与volatile的比较
1.volatile不需要加锁,比synchronized轻量级,不会造成线程阻塞。
2.synchronized既可以保证可见性,又可以保证原子性;而volatile只能保证可见性。
===========================================================================
(文章是看完慕课的视频后写的,感谢 http://www.imooc.com/view/352)
转载请注明出处,谢谢!