java创建一个内存10M的对象_12.《深入理解Java虚拟机》Java内存模型JMM

TPS: 每秒事物处理数是衡量一个服务性能好坏高低的重要指标之一。

1. Java的内存模型

Java虚拟机规范中试图定义一种Java内存模型JMM来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。在此之前,主流程序语言(如C/C++等)直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上并发完全正常,而在另外一套平台上并发访问却经常出错,因此在某些场景下就不许针对不同的平台来编写程序。

Java内存模型即要定义得足够严谨,才能让Java的并发内存访问操作不会产生歧义;Java内存模型也必须定义地足够宽松,才能使得虚拟机的实现有足够的自由空间去利用硬件的各种特性来获取更好的执行速度。经过长时间的验证和修补,JDK1.5(实现了JSR-133)发布之后,Java内存模型已经成熟和完善起来了,下面一起来看一下:

2. 主内存和工作内存

Java内存模型的主要目的是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。注意一下,此处的变量并不包括局部变量与方法参数,因为它们是线程私有的,不会被共享,自然也不会存在竞争;此处的变量应该是实例字段、静态字段和构成数组对象的元素。

Java内存模型规定了所有的变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件时的主内存名字一样,两者可以互相类比,但此处仅仅是虚拟机内存的一部分),每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量和主内存副本拷贝(注意这里绝不会是整个对象的拷贝,试想一个10M的对象,在每个用到这个对象的工作内存中有一个10M的拷贝,内存还受得了?也就是一些在线程中用到的对象中的字段罢了),线程对变量所有的操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。线程、主内存、工作内存三者的交互关系如图:

0818b9ca8b590ca3270a3433284dd417.png

注意:这里讲的主内存、工作内存与Java内存区域中的Java堆、栈、方法区等并不是一个层次的内存划分,两者并没有什么关系。

3. 内存间交互操作

主内存和工作内存之间具体的交互协议:即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节。Java内存模型中定义了以下8种操作来完成,虚拟机实现时必须保证下面的每一种操作都是原子的、不可再分的:

1、lock(锁定):作用于主内存中的变量,它把一个变量标识为一条线程独占的状态。

2、unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

3、read(读取):作用于主内存中的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

4、load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

5、use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。

6、assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

7、store(存储):作用于工作内存中的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。

8、write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量值放入主内存的变量中。

例如:

如果要把一个变量从主内存复制到工作内存,那就要顺序的执行:read和load操作;

如果要把变量从工作内存同步回主内存,就要顺序的执行:store和write操作。

上述这两个操作必须按照顺序执行,但不必保证是连续执行。

Java内存模型还规定了在执行上述8种基本操作的时候必须满足如下规则:

1、不允许read和load、store和write操作之一单独出现。

2、不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

3、不允许一个线程无原因地把数据从线程的工作内存同步回主内存中。

4、一个新的变量只能从主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。

5、一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

6、如果对同一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

7、如果一个变量事先没有被lock操作锁定,那就不允许对它进行unlock操作,也不允许去unlock一个被其他线程锁定的变量。

8、对一个变量执行unlock操作之前,必须先把此变量同步回主内存中。

4. volatile型变量的特殊规则

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制。

一个变量被定义为volatile后,它将具备两种特性:

1、保证此变量对所有线程的”可见性”,所谓”可见性”是指当一条线程修改了这个变量的值,新值对于其它线程来说都是可以立即得知的。即volatile变量在各个不同线程中不存在一致性问题。而普通变量不能做到这一点,普通变量的值在在线程间传递均需要通过主内存来完成。再强调一遍,volatile只保证了可见性,并不保证基于volatile变量的运算在并发下是安全的,因为Java里面的运算并非原子操作。下面通过一个简单的示例来说明原因:

package JVM;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/** * Created by louyuting on 17/1/3. */

public class VolatileTest {

public static volatile int race=0;

public static void increase(){

race++;

}

private static final int THREAD_COUNT=20;

public static void main(String[] args) {

ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

for(int i=0; i

executorService.submit(new Thread(new Runnable() {

@Override

public void run() {

for(int j=0; j<10000; j++){

increase();

}

}

}));

}

executorService.shutdown();

System.out.print(race);

}

}

上面的代码我创建了20个线程,每个线程对race变量进行10000次自增操作,如果能够正确并发的话,正确的结果应该是200000.我运行了几次之后的结果如图所示:

0818b9ca8b590ca3270a3433284dd417.png

0818b9ca8b590ca3270a3433284dd417.png

0818b9ca8b590ca3270a3433284dd417.png

很容易看出每次的运行结果都不一样,甚至差别很大。问题就

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值