Java线程/内存模型的缺陷和增强

Java在语言层次上实现了对线程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着更为复杂的玄机,很多问题就是由此而起。

一、Java内存模型

在了解Java的同步秘密之前,先来看看JMM(Java Memory Model)。

Java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。而且Java语言最大的特点就是废除了指针,把程序员从痛苦中解脱出来,不用再考虑内存使用和管理方面的问题。

可惜世事总不尽如人意,虽然JMM设计上方便了程序员,但是它增加了虚拟机的复杂程度,而且还导致某些编程技巧在Java语言中失效。

JMM主要是为了规定了线程和内存之间的一些关系。对Java程序员来说只需负责用synchronized同步关键字,其它诸如与线程/内存之间进行数据交换/同步等繁琐工作均由虚拟机负责完成。如图1所示:根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

图1 Java内存模型示例图

线程若要对某变量进行操作,必须经过一系列步骤:首先从主存复制/刷新数据到工作内存,然后执行代码,进行引用/赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对线程和主存互操作定义了6个行为,分别为load,save,read,write,assign和use,这些操作行为具有原子性,且相互依赖,有明确的调用先后顺序。具体的描述请参见JLS第17章。

我们在前面的章节介绍了synchronized的作用,现在,从JMM的角度来重新审视synchronized关键字。

假设某条线程执行一个synchronized代码段,其间对某变量进行操作,JVM会依次执行如下动作:

(1) 获取同步对象monitor (lock)

(2) 从主存复制变量到当前工作内存 (read and load)

(3) 执行代码,改变共享变量值 (use and assign)

(4) 用工作内存数据刷新主存相关内容 (store and write)

(5) 释放同步对象锁 (unlock)

可见,synchronized的另外一个作用是保证主存内容和线程的工作内存中的数据的一致性。如果没有使用synchronized关键字,JVM不保证第2步和第4步会严格按照上述次序立即执行。因为根据JLS中的规定,线程的工作内存和主存之间的数据交换是松耦合的,什么时候需要刷新工作内存或者更新主内存内容,可以由具体的虚拟机实现自行决定。如果多个线程同时执行一段未经synchronized保护的代码段,很有可能某条线程已经改动了变量的值,但是其他线程却无法看到这个改动,依然在旧的变量值上进行运算,最终导致不可预料的运算结果。

时间: 2025-01-20 14:32:49

Java线程/内存模型的缺陷和增强的相关文章

Akka学习笔记(五):Akka与Java的内存模型

Akka学习笔记(五):Akka与Java的内存模型 Akka简化了编写并发软件的过程,本文主要讨论Akka如何在并发应用中访问共享内存. Java内存模型 Java5之前的JMM是相当混乱的.多线程访问共享内存很有可能会得奇怪的结果,如: 可见性问题,无法及时看到其他线程写入的值 指令乱序,观测到其他线程不可能的行为 从Java 5的JSR 133的实现,很多问题就解决了.JMM是基于一组"happens-before"关联规则,限制了访问内存的行为必须在另一个内存访问行为之前发生.

JAVA的内存模型及结构

所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢? Java内存模型 Java内存模型在JVM specification, Java SE 7 Edition, and mainly in the chapters "2.5 Runtime Data Areas" and "2.6 Frames"中有详细的说明.对象和类的数据存储在3个不同的内存区域:堆(he

Erlang和Java的内存模型比较

问题描述 我读到一篇相当相当有趣的关于Erlang VM内存**策略的文章.它是Jesper Wilhelmsson写的一篇论文,我觉得有必要讨论一下Erlang和Oracle的Java虚拟机在内存**方面的不同之处. 对于从没听说过Erlang的人来说,有必要简单的介绍一下,它是一门函数式语言,并且使用异步消息传递作为它并发的基石.消息传递使用的是拷贝的机制,并且在不同的Erlang虚拟机中传播,甚至是在不同的机器上(不过这个对程序员来说是透明的). Erlang和Java都通过虚拟机将底层硬

认识JVM--第二篇-java对象内存模型

   前一段写了一篇<认识JVM>,不过在一些方面可以继续阐述的,在这里继续探讨一下,本文重点在于在heap区域内部对象之间的组织关系,以及各种粒度之间的关系,以及JVM常见优化方法,文章目录如下所示: 1.回顾--java基础的对象大概有哪些特征 2.上一节中提到的Class加载是如何加载的 3.一个对象放在内存中的是如何存放的 4.调用的指令分析 5.对象宽度对其问题及空间浪费 6.指令优化   正文如下: 1.回顾--java基础的对象大概有哪些特征?     相信学习过java或者叫做

深入理解Java内存模型(六) final

与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译 器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操 作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序. 下面,我们通过一些示例性的代码来分别说明这两个规则: public class FinalExample { int i; //普通变量 fin

Java笔记:Java内存模型

1. 基本概念 <深入理解Java内存模型>详细讲解了java的内存模型,这里对其中的一些基本概念做个简单的笔记.以下内容摘自 <深入理解Java内存模型>读书总结 并发 定义:即,并发(同时)发生.在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行. 并发需要处理两个关键问题:线程之间如何通信及线程之间如何同步. 通信:是指线程之间如何交换信息.在命令式编程中,线程之间的通信机制

浅谈java内存模型

不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的.其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改.总结java的内存模型,要解决两个主要的问题:可见性和有序性. 我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的.JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要清楚在jvm内存模型的基础上,如果解决多线程的可见性和有序性. 那么,何谓可见性? 多个线程之

JVM学习(3)——总结Java内存模型

俗话说,自己写的代码,6个月后也是别人的代码--复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简介 volatitle关键字 原子性 可见性 有序性 指令重排 先行发生--happen-before原则 解释执行和编译执行 其他语言(c和c++)也有内存模型么?   为什么需要关注Java内存模型?   之前有一个我实习的同事(已经工作的)反讽我:学(关注)这个有什么用? 我没有回答,我牢记一

《深入解析Android 虚拟机》——2.6 Java内存模型

2.6 Java内存模型 不同的平台,内存模型是不一样的,但是JVM的内存模型规范是统一的.其实Java的多线程并发问题最终都会反映在Java内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改.总结Java的内存模型,要解决两个主要的问题:可见性和有序性. 人们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的.JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于Java开发人员,要清楚在JVM内存模型的基础上,如果解决多线程的可见性和有序性. 那么,