黑马-----内存模型和volatile详解
黑马程序员:Java培训、Android培训、iOS培训、.Net培训
JAVA线程-内存模型和volatile详解
一、单核内存模型
1、程序运行时,将临时数据存放到Cache中
2、将CPU计算所需要的数据从Cache中拷贝一份到H Cache中
3、CPU直接从H Cache中读取数据进行计算
4、CPU将计算的结果写入H Cache中
5、H Cache将最新的结果值涮入Cache中(何时写入不确定)
6、将Cache中结果数据写回程序(如果有需要,例如文件、数据库)
需要H Cache的原因:CPU的执行速度很快,而向Cache读取或写入数据则相对慢得多,因此,就需要H Cache来弥补。
二、多核内存模型
有2个线程:ThreadA和ThreadB,分别在不同的CUP内运行,并且执行如下代码:
i = 0; i = i + 1;。最后,我们希望i的值为2。
1、ThreadA和ThreadB分别读取i=0的值存入各自的H Cache中,此时H Cache中的i值都为0;ThreadA和ThreadB分别对i进行计算并得到的结果都为1;2个H Cache分别将结果写入Cache,最终,Cache中i的值为1。显然,这不是我们希望得到的结果。这就是著名的:缓存一致性问题。(对单核CPU也会出现同样的问题,只是单核CPU以线程调度的形式来分别执行)
2、缓存一致性问题的解决方法
1)总线加LOCK#锁(效率低下,不可取)
2)缓存一致性协议(这里不详述)
三、并发的三个概念
1、原子性
1)即一个操作或多个操作,要么全部执行并且执行的过程中不会被任何因素打断,要么不执行。
2)有如下代码:i = 9999999999;
假设为一个32位的变量赋值包括2个过程:为低16位赋值,为高16位赋值。现在,可能会发生:ThreadA将低16位数值写入之后,突然被中断,此时,ThreadB读取i的值就会得到错误的结果。
2、可见性
1)是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到修改的值。
2)ThreadA和ThreadB分别执行如下代码:
ThreadA :int i = 0;
i = 10;
ThreadB :int j = i;
(1)当ThreadA执行到i=10时,首先将i的初始值加载到其CPU的H Cache中,然后赋值为10。那么,ThreadA的H Cache中i的值为10,但是,ThreadA却没有立即将H Cache中i的值马上写入Cache中。
(2)此时,ThreadB开始执行,然而Cache中i的值仍然为0,最终,不管怎样,j的值都为0。而不是我们希望j=10那样。
(3)这正是由于ThreadA对i修改后,ThreadB没有立即看到线程ThreadA对i修改的值。
3、有序性
1)即程序执行的顺序按照代码的先后顺序执行。
2)指令重排序:即处理器为了提高程序运行效率,可能会对输入代码进行优化,它不会保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
3)有如下代码:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a * a; //语句4
可能执行的顺序:1 2 3 4 或 2 1 3 4,不再可能有其它。这是因为处理器需要考虑指令之间的数据依赖性。
4)ThreadA和ThreadB分别执行如下代码:
ThreadA:context = loadContext(); //语句1
inited = ture; //语句2
ThreadB:while(!inited) sleep(); //语句3
doSomething(context); //语句4
如果处理器对ThreadA的指令进行重排,则ThreadB可能在context没有赋值的情况下执行doSomething(context),从而导致程序运行错误。
可见,原子性、可见性、有序性都不会影响单个线程对代码的执行结果。但是,会影并发执行的正确性。想要程序正确的并发执行,必须同时保证原子性、可见性、有序性。
四、Java确保并发执行的正确性
1、原子性
1)在Java中,对基本数据类型的变量的读取和赋值操作是原子性的。
2)下面哪些语句是原子操作的
x = 10; // 是原子操作
x = x + 1; // 不是:首先读取x值,在加1,最后赋值
x ++; // 不是:首先读取x值,在加1,最后赋值
y = x; // 不是:首先读取x值,然后赋值
3)如果实现更大范围的原子性,可使用synchronized和Lock来实现。
2、可见性
1)使用volatile来确保可见性:保证了修改的值会立即被更新到Cache并且使其它线程的H Cache中的volatile变量的值无效。
2)也可使用synchronized和Lock来确保可见性:保证了修改的值在锁被释放之前被更新到Cache并且使其它线程的H Cache中的相关变量的值无效。
3、有序性
1)使用volatile来确保真正的有序性:禁止对volatile变量进行指令重排序。
2)synchronized和Lock也能确保的有序性:以单线程执行同步代码块的方式来实现的。
五、volatile变量的详细论述
1、volatile关键字的两层含义:
1)保证可见性
2)禁止指令重排序,保证有序性
(1)当对volatile变量进行读取或写入操作时,在其前面对该volatile变量的操作早已完成并且结果已对当前操作可见,而当前操作的后序操作肯定还没有执行。
(2)进行指令优化时,不能将操作volatile变量前的指令放到volatile变量后执行,也不能将操作volatile变量后的指令放到volatile变量前执行。
注意:volatile关键字不保证原子性
3)例如:
x = 9; //语句1
y = 8; //语句2
volatile flag = ture; //语句3
i = 7; //语句4
k = 6; //语句5
尽管x,y,i,j之间不存在依赖性,但是,语句4和语句5不会被放到语句3之前执行,而语句1和语句2也不会被放到语句3之后执行。
可能执行的顺序有:1,2,3,4,5 或 2,1,3,4,5,或 1,2,3,5,4
或2,1,3,5,4
2、volatile与synchronized的区别
1)共同点
(1)volatile与synchronized都是同步机制的一部分
(2)都实现了可见性和有序性
2)区别
(1)synchronized实现了原子性,而volatile没有
(2)作用的对象不同:volatile作用的是变量,而synchronized作用的是语句块或方法。
(3)对线程的作用不同:volatile不会阻塞线程,而synchronized会阻塞线程,即volatile没有使用监视器,而synchronized使用了监视器。所以,volatile是一种比synchronized更轻量的弱化的同步机制。
六、volatile的正确使用
1、模式一:状态标记
1)没有使用volatile导致的并发问题
ThreadA :boolean stop = false; ThreadB :stop = ture;
while(!stop){
//A
doSomething();
}
并发可能不会正确执行:即ThreadA进入死循环
原因:1、ThreadA永远只读其H Cache中stop的值
2、ThreadB只将修改了stop的值保存到其H Cache中
3、即使ThreadA偶尔会从Cache中读取stop的值,如果Thread在A处阻
塞,而此时ThreadB修改了stop的值并且写入Cache,由于ThreadA没有看到Cache中stop值已经修改,即使重新执行,也可能会进入死循环。
2)使用volatile解决
ThreadA :boolean stop = false; ThreadB :stop = ture;
while(!stop){
//A
doSomething();
}
2、模式二:Double-check(在单例模式中的使用)
private volatile static Singleton instance;
public stratic Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){ instance = new Singleton();}
}
}
}
这是volatile与synchronized配合使用的经典案例。
3、模式三:开销较低的读-写锁策略
public class CheesyCounter{
//All mutative operations msut be done with the ‘this’lock held
@GuardedBy(“this”) private volatile int value;
public int getVulue(){return value;}
public synchronized int increment(){ return value++;}
}
1)volatile与synchronized配合使用的另一个经典案例
2)如果读操作远远超过写操作,可以结合使用锁和volatile变量来减少公共代码路径的开销,例如本例。
3)计数器必须使用synchronized来确保增量操作是原子的,同时使用volatile保证当前结果的可见性。
4)如果更新不频繁,读取的开销仅仅涉及volatile操作,这由于一个无竞争锁获取的开销。
网上有评论:本模式中value不使用volatile也能实现ThreadSave,因为increment()
使用了synchronized,真的这样吗?答案是否定的,如果ThreadA正进
行increment(),注意synchronized只实现了对increment()的互斥访问,而没有实现对value的互斥访问,而ThreadB也在进行getVulue(),那么ThreadB将会的到一个失效的value值,因为ThreadB不知道ThreadA正在对value进行修改。
4、模式四:一次性安全发布,发布不可变对象或线程安全的对象
public class Test{
public volatile FinalObject object;
public void CreateObject(){object = new FinalObject(…);} //ThreadA
public void doWork(){ //ThreadB
while(ture){ //轮询
if(object != null){doSomething(object);}
}
}
}
1)发布:使对象能够在当前线程作用域之外的代码中使用。
2)如果object引用不是一个volatile型,doWork()对object的引用可能得到一个不完全构造的的FinalObject。
3)必须注意:object本身必须是线程安全的。
4)volatile类型确保了发布形式的可见型,但如果object的状态在发布后可变,那么就需要额外的同步。
5)这个案例还展示出:在不使用阻塞的前提下,阻塞另一个线程的执行(尽管这不是真正的阻塞,但起到了阻塞的作用)。
网上评论:volatile不足以实现安全发布,原因在于object在构造过程中可能被中断。我们应当记得volatile有一个很重要的特性:禁止对volatile变量进行指令重排序。即中断指令要么在object在构造前执行,要么在object在构造后执行,不可能出现在构造过程中。
5、模式五:独立观察
public class UserManager{
public volatile String lastUser;
public boolean authenticate(String userName, String password){
boolean valid = passwordIsValid(user, password);
if(valid){
User u = new User(userName, password);
activeUsers.add(u);
lasstUser = user;
}
return valid;
}
………
}
1)本例展示了身份机制如何记忆最近一次登陆的用户的名字,并将反复使用lastUser引用来发布值,以供程序的其它部分使用。
2)该模式的另一个使用是收集程序的统计信息或定期(不定期)发布信息
3)这个模式要求:(1)被发布的值是有效不可变的-即值的状态在发布后不会更改(下一次发布已经是一个新的值,而不是在原有值的基础上的改变)
(2)使用发布值的代码需要清楚该值可能随时发生变化。
6、模式六:volatile-bean模式
@ThreadSafe
public class Person{
private volatile Stirng name;
private volatile int age;
public String getName(){return name;}
public int getAge(){return age;}
public void setName(String name){this.name = name;}
public void setAge(int age){this.age = age;}
}
1)原理:很多框架为易变数据的持有者(例如HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。
2)volatile-bean模式的所有成员都必须是volatile并且有效不可变,同时只能有getter和setter方法。
四、volatile变量的使用原则
1、写入变量不依赖此变量的值,或只有一个线程修改此变量
2、变量不与其它变量共同参与不变约束
3、访问变量不需要加锁
黑马-----内存模型和volatile详解的更多相关文章
- 并发一:Java内存模型和Volatile
并发一:Java内存模型和Volatile 一.Java内存模型(JMM) Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和在内存中取出变量的底层细节,是围绕着 ...
- Java内存模型:volatile详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt202 Java内存模型:volatile是干什么用的Volatile字段是用 ...
- Java线程角度的内存模型和volatile型变量
内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细 ...
- Java虚拟机内存模型和volatile型变量
Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量 ...
- 不止面试02-JVM内存模型面试题详解
第一部分:面试题 本篇文章我们将尝试回答以下问题: 描述一下jvm的内存结构 描述一下jvm的内存模型 谈一下你对常量池的理解 什么情况下会发生栈内存溢出?和内存溢出有什么不同? String str ...
- Java并发关键字Volatile 详解
Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议 ...
- Java内存模型和JVM内存管理
Java内存模型和JVM内存管理 一.Java内存模型: 1.主内存和工作内存(即是本地内存): Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取 ...
- Java 内存模型和 JVM 内存结构真不是一回事
这两个概念估计有不少人会混淆,它们都可以说是 JVM 规范的一部分,但真不是一回事!它们描述和解决的是不同问题,简单来说, Java 内存模型,描述的是多线程允许的行为 JVM 内存结构,描述的是线程 ...
- Java内存模型相关原则详解
在<Java内存模型(JMM)详解>一文中我们已经讲到了Java内存模型的基本结构以及相关操作和规则.而Java内存模型又是围绕着在并发过程中如何处理原子性.可见性以及有序性这三个特征来构 ...
随机推荐
- [MISSAJJ原创] UITableViewCell移动及翻转出现的3D动画效果[58同城cell移动效果]
2015-11-20 很喜欢在安静的状态, 听着音乐,敲着键盘, 和代码们浓情对话, 每一份代码的积累, 都让自己觉得很充实快乐!Y(^_^)Y. 看到58同城app的cell有动画移动出现的特效,很 ...
- session放入缓存(redis)、DB
为什么要把SESSION保存在缓存 就php来说,语言本身支持的session是以文件的方式保存到磁盘文件中,保存在指定的文件夹中,保存的路径可以在配置文件中设置或者在程序中使用函数session_s ...
- 盘点JavaScript里好用的原生API
转自:https://segmentfault.com/a/1190000002753931 解析字符串对象 我们都知道,JavaScript对象可以序列化为JSON,JSON也可以解析成对象,但是问 ...
- C# 模拟webform里面按钮的点击事件
生成的html内容 <body> <form method="post" action="./Login.aspx" id="for ...
- WebService的两种方式Soap和Rest比较
我的读后感:由于第一次接触WebService,对于很多概念不太理解,尤其是看到各个OpenAPI的不同提供方式时,更加疑惑.如google map api采用了AJAX方式,通过javascript ...
- CentOS 6中MATLAB print函数“所见非所得”bug的解决方案
0 系统配置+软件版本 主机:Dell optiplex 390 MT (i5) 系统+软件:CentOS 6.5 x64, Matlab R2012, R2013 系统+软件:CentOS 6.7 ...
- CentOS6.5 下安装 texlive2015 并设置 ctex 中文套装
0 卸载旧版本的 texlive 0.1 卸载 texlive2007 如果系统没有安装过texlive,则跳过第0步. 可以在终端中使用如下命令查询本机已经安装的tex和latex版本: [She@ ...
- CALayer 3 详解 -----转自李明杰
CALayer3-层的属性 本文目录 一.隐式动画属性 二.position和anchorPoint 回到顶部 一.隐式动画属性 * 在前面几讲中已经提到,每一个UIView内部都默认关联着一个C ...
- Jquery开灯关灯效果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- gbk与utf-8转换
linux: #include <iconv.h> int code_convert(char *from_charset,char *to_charset,char *inbuf,int ...