Java内存模型(一)

Java内存模型描述了Java程序中各种变量(共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取这些变量的底层细节。

  • 主存:所有共享变量都保存在主存中。
  • 工作内存:每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本。

两条规定:

  • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主存中读写。
  • 不同线程的工作内存之间无法直接相互访问,线程之间的变量传递,必须通过主存来完成。

在开始并发编程时,我们需要思考两个关键的问题:1.线程之间如何通信?2.线程之间如何同步?

线程通信

在命令式编程中,线程之间有两种通信方式:

  1. 共享内存:线程之间存在公共状态,线程间通过独写内存中的公共状态来隐式进行通信。
  2. 消息传递:线程之间没有公共状态,需要发送消息来进行显式通信。

线程同步

同步是指程序用于控制线程发生相对顺序执行的机制。在共享内存模型里,程序员需要给代码加上制定的互斥操作来显式进行;在消息传递模型中,通信是对程序员透明的,是隐式进行的。

可见性

所有的实例域、静态域、数组元素是储存在堆中的,线程之间可以共享,可以将它们称为“共享变量”,他们可能会在并发编程时出现“可见性”问题;而局部变量、方法参数、异常处理参数不会在线程之间共享,不受内存模型的影响。

假如一个变量被多个线程使用到,那么这个共享变量会在多个线程的工作内存中都存在副本。

当一个共享变量被一个线程修改,能够及时被其他线程看到,这叫做可见性。

要实现可见性,需要保证两点:

  • 共享变量被修改后,能及时刷新到主存中去。
  • 其他线程能及时将主存中更新的信息刷新到自己的工作内存中

重排序

as-if-serial语义:

  • 无论怎么重排序,程序执行的结果必须是与未排序情况下一致的。(Java保证在单线程情况下遵循词语义)
  • 多线程中程序交错执行时,重排序可能会导致内存可见性问题。

数据依赖性

如果两个操作访问同一个变量,而且这两个操作中有一个为写操作,那么这两个操作之间就存在了数据依赖性。数据依赖性存在以下三种情况:

操作示例
先写,后读a=1;b=a;
先写,后写a=1;a=2;
先读,后写b=a;a=1;

不难发现,上面的三种情况,只要重排序其指令,结果都会产生变化。

所以编译器和处理器在进行重排序时,必须遵守数据依赖性。不能对存在数据依赖性的两个操作进行重排序。

控制依赖性

看下面一段代码

if(flag){ //操作1
    int num=a+b; //操作2
}
复制代码

可以看到,操作1和操作2并不存在数据依赖,但是存在控制依赖。当代码中出现控制依赖时,会影响程序的并行度。因此,编译器和处理器会采用一种“猜测执行”来克服控制依赖性对并行度的影响(并行是为了效率和性能)处理器可能提前执行操作2,将a+b计算出来,并放置到一个叫“重排序缓存”的缓存中,假如得知操作1中的flag为真,再将结果写入num中。

在单线程程序中,对存在控制依赖关系的重排序,不会影响结果;不过在多线程中,可能会影响到结果。

指令重排序:

实际执行的代码顺序和程序员书写的顺序是不一样的,编译器或处理器为了提高性能,在不影响程序结果的前提下,会进行执行顺序的优化。

  • 编译器优化重排序(编译器)
  • 指令级并行重排序(处理器)
  • 内存系统重排序(处理器)

导致不可见的原因:

  • 线程的交叉执行(原子性问题)
  • 重排序结合线程交叉执行(原子性问题)
  • 共享变量更新后的值,没有在工作内存和主存之间得到及时的更新。(可见性问题)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值