加入收藏 | 设为首页 | 会员中心 | 我要投稿 威海站长网 (https://www.0631zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 资源网站 > 空间 > 正文

深入理解Java虚拟机(高效并发)

发布时间:2019-07-27 23:57:21 所属栏目:空间 来源:张磊BARON
导读:高效并发是 JVM 系列的最后一篇,本篇主要介绍虚拟机如何实现多线程、多线程间如何共享和竞争数据以及共享和竞争数据带来的问题及解决方案。 一. Java 内存模型与线程 让计算机同时执行多个任务,不只是因为处理器的性能更加强大了,更重要是因为计算机的
副标题[/!--empirenews.page--]

高效并发是 JVM 系列的最后一篇,本篇主要介绍虚拟机如何实现多线程、多线程间如何共享和竞争数据以及共享和竞争数据带来的问题及解决方案。

深入理解Java虚拟机(高效并发)

一. Java 内存模型与线程

让计算机同时执行多个任务,不只是因为处理器的性能更加强大了,更重要是因为计算机的运算速度和它的存储以及通信子系统速度差距太大,大量的时间都花费在磁盘 I/O 、网络通信和数据库访问上。为了不让处理器因为等待其它资源而浪费处理器的资源与时间,我们就必须采用让计算机同时执行多任务的方式去充分利用处理器的性能;同时也是为了应对服务端高并发的需求。而 Java 内存模型的设计和线程的存在正是为了更好、更高效的实现多任务。

1.硬件与效率的一致性

计算机中绝大多数的任务都不可能只靠处理器计算就能完成,处理器至少要和内存交互,如读取数据、存储结果等等,这个 I/O 操作是很难消除的。由于计算器的存储设备和处理器的运算速度有几个量级的差距,所以计算机不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓冲:将运算需要用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存中,这样处理器就无需等待缓慢的内存读写了。

基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,因为它引入了一个新的问题:缓存一致性。在多处理器中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。为了解决一致性的问题,需要各个处理器的访问缓存时都遵循一些协议,在读写时要根据协议来进行操作。

深入理解Java虚拟机(高效并发)

除了增加高速缓存外,为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致,但不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,因此,如果存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。与处理器的乱象执行优化类似,JIT 编译器中也有类似的指令重排优化。

2.Java 内存模型

Java 虚拟机规范中定义了 Java 内存模型,用来屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。像 C/C++ 这类语言直接使用了物理硬件和操作系统的内存模型,因此会由于不同平台上内存模型的差异,需要针对不同平台来编写代码。

主内存与工作内存

Java 内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中读取变量这样的底层细节。这里说的变量和 Java 代码中的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括变量和方法参数,因为后者是线程私有的,不会被共享。为了获得较好的执行性能,Java 内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制 JIT 编译器进行代码执行顺序这类优化措施。

Java 内存模型规定了所有的变量都存储在主内存,每条线程都有自己单独的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存,线程间变量值的传递均需要通过主内存来完成。

深入理解Java虚拟机(高效并发)

内存间交互操作

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

这 8 种操作分别是:lock(锁定)、unlock(解锁)、read(读取)、load(载入)、use(使用)、assign(赋值)、store(存储)、write(写入)。

对 volatile 型变量的特殊规则

volatile 是 Java 虚拟机提供的最轻量级的同步机制。当一个变量被定义为 volatile 后,它将具备两种特性:

第一是保证此变量对所有线程的可见性,这里的「可见性」是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。普通变量则做不到这一点,需要通过主内存来在线程间传递数据。比如,线程 A 修改了一个普通的变量值,然后向主内存进行回写,另一条线程 B 在 A 线程回写完成之后再从主内存进行读写操作,新变量值才会对线程 B 可见。

第二是禁止指令重排优化。普通变量仅仅会保证方法的执行过程中所有依赖赋值结果的地方能够获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。因为在一个线程的方法执行过程中无法感知到这点,这也就是 Java 内存模型中描述的所谓的「线程内表现为串行的语义」。

对 long 和 double 型变量的特殊规则

Java 内存模型要求 lock、unlock、read、load、assign、use、store、writer 这 8 个操作都具有原子性,但对于 64 位数据类型(long 和 double),在模型中特别定义了一条相对宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性。这点就是所谓的 long 和 double 的非原子协定。

如果有多个线程共享一个未声明为 volatile 的 long 或 double 类型的变量,并且同时对它们进行读取和修改操作,那么某些线程可能会读取到一个错误的值。好在这种情况非常罕见,主流商业虚拟机中也都把对 long 和 double 的操作视为原子性,因此在实际开发中无需使用 volatile 来修饰变量。

原子性、可见性和有序性

Java 内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性 3 个特质来建立的。

原子性(Atomicity)

(编辑:威海站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读