JVM(三)内存模型
2015-01-24 18:13
176 查看
在第一篇里面提到过.java内存模型(Java Memory Model,JMM):主要目标是用来定义程序中各个变量的访问规则,来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果.
换句话说, java如何实现一次编写,到处运行? 内存模型的实现就是屏蔽掉各个平台的差异. 内存模型我感觉类似一个规范.在不同平台会有不同的实现.就像是接口与实现类的关系.
我们还是先来提出问题:
1.jvm中在内存模型层面,内存是怎么划分的?
2.内存间是怎么交互的,有什么规则?
3.java内存模型的三大特征是什么?
下面来解决问题:
1.jvm中在内存模型层面,内存是怎么划分的?
内存划分为:主内容,工作内存. 关系如下图:
图中可以看到,每个线程有自己的工作内存,从主内存读取数据(会在工作内存创建一个副本),然后再回写到主内存. ,线程之间是没办法直接通讯的,必须通过主内存来进行通讯. 而内存模型就是用来定义他们之间通讯的规则,解决并发的问题.
2.内存间是怎么交互的?
通过第一点,我们知道,线程是和主内存直接交互的. 那交互都有什么样的操作呢?
jmm定义了8种. 分别是 lock(锁定),unlock(解锁),read(读取),load(载入),use(使用),assign(赋值),store(存储),write(写入)
这8种操作的名称,基本就解释了他们要干什么.
我们假设线程a需要把主内存的变量x拿来+1,再返回. 那么顺序就是 read,load,user,assign,store.write 这样的顺序.
对于这些操作,jmm规定了一些规则:
1):不允许read和load, store和write操作之一单独出现.就是不能那了一半,或者给了一半.很好奇为什么不把这2个动作整合成一个呢?
2):不允许一个线程丢弃它最近的assign操作.就是改了就要告诉主内存.
3):不允许一个线程无原因的(没发生任何assign操作),把数据从线程的工作内存同步到主内存.
4):一个新变量只能在主内存中诞生.
5):一个变量在同一时刻只允许一条线程对其进行lock操作,但这个线程可以多次执行lock,执行对应次数的unlock后解锁.
6):如果对一个变量执行lock操作,将会清空工作中此变量的值.在执行引擎使用这个变量前,重新执行load或者assign操作初始化变量的值.
7):如果一个变量没有被lock,那就不允许对他unlock,也不允许unlock其他线程lock的变量.
8):在对变量执行unlock前,必须把变量同步到主内存
这些规则看起来都很容易理解,除了这些规则, 对volatile,long,double类型变量,还有一些特殊规则.volatile后面再说.long,double是因为他们是64位的数据类型,jmm允许按32位来读取2次获得,所以会出现读取到一半的情况,不过jvm一半都会做处理,所以我们不用考虑.
3.java内存模型的三大特征是什么?
上面讲了内存交互的操作和规则,这些规则其实是为模型的三大特征服务的. 三大特征: 原子性(atomicity) 可见性(visibility) 有序性(ordering)
原子性: 操作的8种类型都是具有原子性的.但是我们平时写代码,基本上是需要更大范围的原子性, 怎么处理呢. 使用lock和unlock就行了.
但是问题来了....java没有把这2个操作放开让程序员写,怎么办? 使用synchronize就行了.这是java提供的保证更大范围原子性的关键字.
可见性:可见性就是说哪个线程该了,就要同步到主内存, 我们上面提到过volatile特殊规则, volatile修饰的变量,会在使用前再从主内存刷新一次.所以volatile变量能保证可见性,普通变量不能保证.
普通变量怎么保证呢? 还是使用 synchronize就可以了.
有序性:有序性是针对cpu的指令重排序来说的. 指令重排序某些特殊情况下会影响程序结构. 所以有时候需要禁止重排序,达到有序性. java可以通过volatile 和 synchronize来保证有序性.
OK.问题讲完了. 上面这些概念,都是从书上照搬的.下面来谈谈实际应用.
1.volatile修饰的变量,还需要同步吗?
看情况,例如需要对这个变量进行修改操作,而这个修改操作是不同步的,就会有问题.
2.事务的原子性和这个是一个概念吗?
原子性的概念是一致的.
3.如果我有个方法a,是个计数器功能,调用一次+1.如何保证计数器正确?
将方法a设置为synchronize,
4.假设方法a用于更新 剩余数量, 每次执行前先从数据库查询出原始数量X,再减去操作数量Y,再更新到数据库. 那么将方法a设置为synchronize是否能保证数据正确?
不能保证. 例如线程T1 的方法M1调用a方法读取原始100.减去10,剩余90执行更新,但是事务没提交,方法a的锁已经释放,此时线程T2的方法M2调用a方法读取到原始100,减去20,剩余80执行更新.结果就会出现更新丢失的情况.
问题原因分析: 可以说线程T1,T2互相影响到了对方.违背了事务的隔离性.
解决方案: 1.整个事务级别进行同步, 即M1,M2级别进行同步
2.数据库层面加锁,例如悲观锁,必须M1方法提交,M2才可以操作.
换句话说, java如何实现一次编写,到处运行? 内存模型的实现就是屏蔽掉各个平台的差异. 内存模型我感觉类似一个规范.在不同平台会有不同的实现.就像是接口与实现类的关系.
我们还是先来提出问题:
1.jvm中在内存模型层面,内存是怎么划分的?
2.内存间是怎么交互的,有什么规则?
3.java内存模型的三大特征是什么?
下面来解决问题:
1.jvm中在内存模型层面,内存是怎么划分的?
内存划分为:主内容,工作内存. 关系如下图:
图中可以看到,每个线程有自己的工作内存,从主内存读取数据(会在工作内存创建一个副本),然后再回写到主内存. ,线程之间是没办法直接通讯的,必须通过主内存来进行通讯. 而内存模型就是用来定义他们之间通讯的规则,解决并发的问题.
2.内存间是怎么交互的?
通过第一点,我们知道,线程是和主内存直接交互的. 那交互都有什么样的操作呢?
jmm定义了8种. 分别是 lock(锁定),unlock(解锁),read(读取),load(载入),use(使用),assign(赋值),store(存储),write(写入)
这8种操作的名称,基本就解释了他们要干什么.
我们假设线程a需要把主内存的变量x拿来+1,再返回. 那么顺序就是 read,load,user,assign,store.write 这样的顺序.
对于这些操作,jmm规定了一些规则:
1):不允许read和load, store和write操作之一单独出现.就是不能那了一半,或者给了一半.很好奇为什么不把这2个动作整合成一个呢?
2):不允许一个线程丢弃它最近的assign操作.就是改了就要告诉主内存.
3):不允许一个线程无原因的(没发生任何assign操作),把数据从线程的工作内存同步到主内存.
4):一个新变量只能在主内存中诞生.
5):一个变量在同一时刻只允许一条线程对其进行lock操作,但这个线程可以多次执行lock,执行对应次数的unlock后解锁.
6):如果对一个变量执行lock操作,将会清空工作中此变量的值.在执行引擎使用这个变量前,重新执行load或者assign操作初始化变量的值.
7):如果一个变量没有被lock,那就不允许对他unlock,也不允许unlock其他线程lock的变量.
8):在对变量执行unlock前,必须把变量同步到主内存
这些规则看起来都很容易理解,除了这些规则, 对volatile,long,double类型变量,还有一些特殊规则.volatile后面再说.long,double是因为他们是64位的数据类型,jmm允许按32位来读取2次获得,所以会出现读取到一半的情况,不过jvm一半都会做处理,所以我们不用考虑.
3.java内存模型的三大特征是什么?
上面讲了内存交互的操作和规则,这些规则其实是为模型的三大特征服务的. 三大特征: 原子性(atomicity) 可见性(visibility) 有序性(ordering)
原子性: 操作的8种类型都是具有原子性的.但是我们平时写代码,基本上是需要更大范围的原子性, 怎么处理呢. 使用lock和unlock就行了.
但是问题来了....java没有把这2个操作放开让程序员写,怎么办? 使用synchronize就行了.这是java提供的保证更大范围原子性的关键字.
可见性:可见性就是说哪个线程该了,就要同步到主内存, 我们上面提到过volatile特殊规则, volatile修饰的变量,会在使用前再从主内存刷新一次.所以volatile变量能保证可见性,普通变量不能保证.
普通变量怎么保证呢? 还是使用 synchronize就可以了.
有序性:有序性是针对cpu的指令重排序来说的. 指令重排序某些特殊情况下会影响程序结构. 所以有时候需要禁止重排序,达到有序性. java可以通过volatile 和 synchronize来保证有序性.
OK.问题讲完了. 上面这些概念,都是从书上照搬的.下面来谈谈实际应用.
1.volatile修饰的变量,还需要同步吗?
看情况,例如需要对这个变量进行修改操作,而这个修改操作是不同步的,就会有问题.
2.事务的原子性和这个是一个概念吗?
原子性的概念是一致的.
3.如果我有个方法a,是个计数器功能,调用一次+1.如何保证计数器正确?
将方法a设置为synchronize,
4.假设方法a用于更新 剩余数量, 每次执行前先从数据库查询出原始数量X,再减去操作数量Y,再更新到数据库. 那么将方法a设置为synchronize是否能保证数据正确?
不能保证. 例如线程T1 的方法M1调用a方法读取原始100.减去10,剩余90执行更新,但是事务没提交,方法a的锁已经释放,此时线程T2的方法M2调用a方法读取到原始100,减去20,剩余80执行更新.结果就会出现更新丢失的情况.
问题原因分析: 可以说线程T1,T2互相影响到了对方.违背了事务的隔离性.
解决方案: 1.整个事务级别进行同步, 即M1,M2级别进行同步
2.数据库层面加锁,例如悲观锁,必须M1方法提交,M2才可以操作.
相关文章推荐
- 深入理解JVM—JVM内存模型
- 深入理解Java虚拟机笔记--JVM内存模型及溢出问题总结
- JVM学习之一.了解jvm的内存模型
- 图解JVM内存模型
- JVM并发机制探讨—内存模型、内存可见性和指令重排序
- Java基础——JVM内存模型
- jvm - 内存模型与线程
- JVM基础(1)——内存模型
- JVM内存模型及垃圾回收算法
- jvm的stack和heap,JVM内存模型,垃圾回收策略,分代收集,增量收集(转)
- JVM 内存模型和垃圾回收(三): 并行回收器
- JVM内存模型
- 深入理解 JVM 系列:Java 的内存模型
- JVM内存模型、指令重排、内存屏障概念解析
- JVM调优之---常见内存模型总结
- 干货|JVM内存模型和常规问题定位手段
- JVM虚拟机内存模型以及GC机制
- 深入理解JVM -- 内存模型及内存溢出(OOM/Out of Memory)
- JVM内存模型图
- 理解 JVM:Java 内存模型(三)—— 锁