首先,为什么需要有内存模型?
因为我们的程序(不管是什么程序)在运行的时候,数据和代码都是放在内存中的,在一个程序的执行过程中,都需要读取和写入数据的。在java中,可以是对本地变量的读写、对对象后属性的读写、或者是对类变量的读写等等。这些读写操作都是需要与内存进行交互的(这一点是由计算机的体系结构决定的),那么在与内存进行交互的过程中,对一个语言来说,哪些交互是合法的?语言本身又对这些读写操作提供什么样的保证?这些问题就需要有一个明确的规范来说明。只要编译器或者运行时环境的厂商根据这个说明来实现语言的编译器或者运行时环境,那么使用同一种语言编写的程序就能够在这些由不同厂商提供的编译器或者运行是环境中都能够正确运行。
Java的内存模型也是起到了这个作用。它的主要目的是用来说明在多线程环境下,什么样的值对于读操作来说是合法的。
已经有很多文章和书籍在谈论java的内存模型,这些文章和书籍基本都会介绍什么是java内存模型以及在多线程环境下我们应该怎么利用java语言提供的支持来保证程序运行的正确性。本文不打算重复这些内容,而是想从另外一角度来尝试说明在多线程环境下,java的内存模型能够给程序员带来哪些最基本的数据同步方面的保证,这里说的最基本是指不使用synchronize或者任何其他java提供的同步构件。
1. Java中的原子操作
在java中,对除了long和double类型之外的基本数据类型,以及引用的付直和读取操作都是原子的,java的运行时环境会保证不会读取和写入不完整的值。但是对于long和double来说,在多线程环境下,一个线程第一次访问一个long或者double的时候,只看到了写入的前4个字节,在后续的访问中,才会看到后面也写入的4个字节。
上面的描述告诉我们,对于非long或者double的数据类型来说,我们在写付值语句的时候是不需要任何特殊同步构件的。但是这只是让程序员不用担心会读到不完整的数据,与可见性是没有任何关系的。也就是说,对于一个可以被多个线程贡献的变量来说,虽然对这个变量的付值或者读取都不用担心数据完整性的问题,但是不同线程仍然可以看到不同版本的值,也就是数据一致性的问题仍然是存在的。这是由于现代计算机的CPU为了减少对内存的访问次数,提高执行效率,往往会采取寄存器缓存的策略,将程序中用到的一些数据线缓存到寄存器中,读取的时候直接访问寄存器。这样就有可能出现内存中的数据已经修改,但是程序执行过程中用到的仍然是寄存器中缓存的过期值的情况。这样的现象在多核CPU架构下尤为严重。
那么,在java中,我们有没有办法让程序在读取一个数据的时候始终从内存加载而不是读取放在寄存器中的值呢?答案是肯定的,我们可以使用java提供的volatile关键字来声明一个共享变量。这样java程序在执行的过程中,碰到被声明为volatile的数据时,都会直接从内存读取该变量的最新值。另外,volatile关键字还有另外一个附加的作用,就是保证对long或者double类型数据在读取和写入时的完整性。
稍微总结一下,如果共享变量的声明没有加上volatile关键字,那么java的运行时环境只能保证除了long和double之外的数据类型的读写完整性,而如果共享变量的声明加上了volatile关键字,那么java能够保证所有数据类型的读写完整性以及他们的一致性(这里说称是可见性可能更加准确一点)。
2. 正确同步的程序
在不使用java提供的任何同步构件时(volatile关键字除外),java的运行时环境能够在一致性和完整性上提供给程序员的帮助也就仅限于第一节所给出的描述了。由此可见,java的内存模型给程序员提供的帮助实在是少的可怜,为了保证程序的在多线程环境下的正确执行,需要程序员付出很多的努力。虽然java本身也在sdk中提供了很多的同步构件,但是程序员在编写复杂的并发程序时仍然会碰到很多挑战,需要花费很多的时间去进行调试,保证程序访问数据的一致性和完整性,避免程序出现死锁。但是,即便是这样,也很难保证并发程序的正确性,有些bug是极其不容易发现的。目前的一些专门为并发程序设计的语言,比如scala,在给程序员减负方面做的应该会更好一点(我也没有看过,只是猜测)。
那么java或者与其相似的一些程序语言来说,怎样才算是一个正确同步的程序呢?首先来看几个概念。
线程间操作:线程间操作是指这样的一些操作,这些操作被一个线程执行之后,会被其他线程察觉到或者影响到其他线程的执行。在java中,对共享变量的读写、对volatile变量的读写、进出synchronized方法或者块、显示的加锁和解锁、线程生命周期中的第一个以及最后一个操作、启动线程的指令、监听线程结束的指令以及对外部系统执行的操作(比如远程方法调用、网络IO、磁盘IO等)等,都是线程间操作;
编程顺序:编程顺序是指所有线程间操作的一个全局顺序。在程序执行期间不同的线程可能会执行不同的线程间操作,编程顺序就是不同线程所执行的线程间操作的一个全局顺序。这个顺序与单线程环境下,线程间操作被执行的顺序一致;
顺序一致性:顺序一致性是指,如果所有线程执行线程间操作的顺序满足编程顺序,并且每一次读都能看到上一次对同一变量执行的写操作的结果,那么整个程序就是顺序一致的(读一定发生在写之后,且读之前只有一次写入操作);
数据竞争:数据竞争是指两个对同一个变量进行访问的线程,至少有一个执行的是写作;
并发程序中碰到的各种错误,一般都是因为程序中共享变量或者程序状态的最新值没有被正在执行的多个线程及时获取到,导致执行失败或者出错。而能够操作共享数据或者状态的操作都是线程间操作,不同线程执行的线程间操作往往既包含读操作也包含写操作,这就会引起数据竞争。所以,归根结底数据竞争是并发编程中的错误根源所在。
要编写能够正确运行的并发程序,我们就需要解决数据竞争问题。从概念上来说,一个程序会被周而复始反复地执行,如果每一次执行都能够满足顺序一致性,并且相互之间不会引起数据竞争,那么这个程序就能够正确地执行。
下面举一个简单的例子,图1中给出了一个简单程序的执行流程图,我们要关注的就是n=n+1这个步骤。在下面的例子中,我们只考虑数据竞争的问题,假设n能够被所有线程看到(比如为n的声明加上了volatile关键字)。
我们知道,n=n+1这个语句在真正执行的时候至少要分为三条语句执行,首先要读取n的最新值,然后执行n+1操作,然后将n+1的值写回到n中。我们假设,n=n+1就是执行了这个操作,拆解开来就是:
k = n;
k = k + 1;
n = k;
上面三条指令中,由于k时局部变量,所以对k的操作是不会影响到其他线程的,而1和3分别是读取n和为n赋值,这两个操作都是线程间操作,因此是会给其他线程带来影响的。
在并发环境下,我们假设,每个线程都能完成对整个程序的一次执行,这样从每个线程的角度来看它们的执行过程是满足顺序一致性的,但是由于每个线程对n都即进行了读又进行了写,因此就产生了数据竞争。所以,在不采取任何措施的情况下,这个程序是无法正确运行的。
为了让程序能够正确地执行,我们就要想办法消除数据竞争,最简单的办法就是将n=n+1放入到同步块中。这样,每次就只有一个线程能够执行n=n+1这个语句,因此也就消除了数据竞争。
当然,上面这个例子过于简单,实际的情况要复杂很多。我们在编写并发程序的时候,要考虑的主要问题就是如何消除数据竞争,并且保证程序中的所有线程间操作在不同的执行过程中,都是保持相同顺序被执行的,这样我们就可以保证程序的正确性。当然,说起来似乎是很简单的,但是实践起来往往非常困难,需要不断地练习和经验的积累。
- 大小: 40.6 KB
分享到:
相关推荐
java 内存模型 java 内存模型 java 内存模型 java 内存模型
Java内存模型是Java虚拟机规范中定义的一部分,它规定了Java程序中变量的读写行为,以及线程之间的交互规则。理解Java内存模型对于编写正确、高效的多线程程序至关重要。在Java 5之前,Java内存模型的描述比较模糊,...
在深入理解Java内存模型之前,我们需要先了解并发编程模型的分类,然后掌握Java内存模型的基础知识,理解重排序和顺序一致性,以及volatile关键字的相关知识点。 首先,让我们探讨Java内存模型的基础知识。在并发...
在Java内存模型中,锁能保证多个线程对共享资源访问的原子性和可见性。例如,synchronized关键字可以用于同步方法或者代码块,实现线程间的互斥访问。而java.util.concurrent.locks包中的锁接口,如ReentrantLock,...
深入理解Java内存模型,不仅能够帮助我们编写出高效、线程安全的代码,还能在面临并发问题时提供有力的分析和解决手段。通过阅读《深入理解Java内存模型》这本书,开发者可以进一步掌握Java并发编程的核心技术,提升...
这些文档如"Java内存模型.docx"、"Java内存模型2.docx"、"深入Java核心 Java内存分配原理精讲.docx"、"java内存模型.pdf"将深入探讨这些概念,帮助开发者更深入地理解Java内存模型及其在实际编程中的应用。...
### Java内存模型的历史变迁 #### 一、引言 随着多核处理器的普及与高性能计算需求的增长,Java作为主流编程语言之一,...随着技术的发展,我们有理由相信Java内存模型将继续进化,以适应未来更加复杂的应用场景。
Java内存模型(Java Memory Model,JMM)是Java平台中非常关键的概念,它定义了线程如何共享和访问内存中的数据,以及在多线程环境下如何保证数据的一致性。这本书"深入理解Java内存模型"显然是为了帮助读者深入探讨...
### Java内存模型详解 #### 1. JMM简介 ##### i. 内存模型概述 Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)的一部分,用于规定程序中的各种变量(包括实例字段、静态字段和数组元素等)在多个...
Java内存模型详解JMM Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)中的一种内存模型,它描述了程序中各个变量之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节...
Java内存模型(Java Memory Model,简称JMM)是Java虚拟机(JVM)规范中定义的一种内存模型,它涉及了线程之间共享变量的可见性问题。在并发编程中,理解Java内存模型对于编写正确的多线程程序至关重要。 首先,...
Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在多线程环境中如何保证数据一致性。理解JMM对于编写高效、正确且线程安全的Java代码至关重要。 ...
Java内存模型(JMM)是Java虚拟机(JVM)的一部分,它定义了程序中不同变量如何交互,特别是在多线程环境下。JMM确保了在各种硬件和操作系统平台上,Java程序的行为具有一致性和可预测性。Java内存模型的主要目标是...
Java 内存模型(Java Memory Model,简称 JMM)是 Java 平台中关于线程如何访问共享变量的一套规则,它定义了线程之间的内存可见性、数据一致性以及指令重排序等关键概念,对于多线程编程和并发性能优化至关重要。...
Java内存模型(JVM Memory Model,简称JMM)是Java平台中的一个重要概念,它定义了程序中各个变量的访问规则,以及在多线程环境下的内存一致性效果。JMM主要解决的是并发环境下不同线程之间如何共享数据以及如何保证...
Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它规定了程序中各个线程如何访问共享变量,以及对这些访问进行同步控制的规则。理解Java内存模型对于编写多线程并发程序至关...
首先,我们来看看Java内存模型。 Java内存模型,通常被称为JVM内存模型,它定义了程序中不同部分如何访问和共享数据。在Java中,内存分为以下几个主要区域: 1. **Java栈**:每个线程都有自己的Java栈,用于存储...
Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main ...