java内存模型(一)

来源:互联网 发布:多益网络后 编辑:程序博客网 时间:2024/05/11 17:06

什么是内存模型?为什么要使用内存模型

内存模型需要解决的问题是,在两个线程里读取同一个值得时候,如何才能使线程读取到最新的值;JAVA内存模型是通过各种操作来定义的,包括对变量的读/写操作。监视器的加锁和释放操作,以及线程的启动和合并操作。JMM为程序中所有的操作定义了一个偏序关系(Happens-Before)。要想保证执行B操作的线程看到A执行后的结果,那么两者之间必须满足这个关系。如果缺少这种关系,那么编译器可以进行任意的重排序;

内存模型的基础

在并发编程中,需要处理的两个关键问题是:

线程之间如何通信,以及线程之间如何同步。通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递;

共享内存

共享内存是指在并发模型里,线程之间共享程序的公共专题改,通过写-读内存中的公共状态进行隐式通信;


消息传递

消息传递是指在并发模型里,线程之间没有公共状态,线程之间必须通过发生消息来显式进行通信;


同步

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间相互排斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的

重排序

在执行程序时,为了提高性能,编辑器和处理器常常会对指令做重排序。重排序分为三种类型;

1)编译器优化的重排序。编辑器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。


public class PossibleReordering {static int x=0,y=0;static int a=0,b=0;public static void main(String[] args) throws Exception{Thread t1=new Thread(new Runnable(){@Overridepublic void run() {a=1;x=b;}});Thread t2=new Thread(new Runnable(){@Overridepublic void run() {b=1;y=a;}});t1.start();t2.start();t1.join();t2.join();System.out.println("x="+x+",y="+y);}}
上面这段程序是一个很经典的重排序例子,因为它里面的每一个顺序都有可能会经过重排序去执行,所以它的结果很难预测。内存级的重排序会使得冲虚的行为变得不可预测。如果没有同步,想预测执行顺序会非常困难,JMM属于语言级的内存模型,它确保在不同的编辑器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为开发者提供一致的内存可见性;



Happens-Before规则

程序顺序规则如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行;

监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作执行。

volatile变量规则:对volatile变量的写入操作必须在对该变量的读操作之前执行。

线程启动规则:在线程上对ThreadStart的调用必须在该线程中执行任何操作前执行;

线程结束规则:线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false;

中断规则:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterrupedException,或者调用isInterrupted和interrupted)。

终结器规则:对象的构造函数必须在启动该对象的终结器之前执行完成。

传递性:如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。


上面这个图很好的阐述了Happens-Before的关系;在线程A的内部所有操作都按照顺序执行,线程B也是如此;

由于A释放了锁M,随后B获得了锁M,因此A中所有在释放锁之前的操作,也就满足在B获得锁之前的所有操作。



1 0
原创粉丝点击