您的位置:首页 > 其它

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才可以操作.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: