`
qindongliang1922
  • 浏览: 2188382 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
7265517b-f87e-3137-b62c-5c6e30e26109
证道Lucene4
浏览量:117658
097be4a0-491e-39c0-89ff-3456fadf8262
证道Hadoop
浏览量:126062
41c37529-f6d8-32e4-8563-3b42b2712a50
证道shell编程
浏览量:60010
43832365-bc15-3f5d-b3cd-c9161722a70c
ELK修真
浏览量:71396
社区版块
存档分类
最新评论

Java进阶之内存模型介绍

    博客分类:
  • JAVA
阅读更多
# Java进阶之内存模型介绍

### 前言

不管在什么编程语言里面,读取和写入都是我们程序最普遍的操作,在单线程的程序里面我们可能不关注线程的读写问题,但是一旦到多线程的环境下,读和写就会变得非常敏感。Java内存模型实际上是定义了在多线程环境下使用读和写操作结果一致性的问题。这个模型在JDK5中通过JSR-133议案进行了修订。


### 为什么需要Java内存模型
主要的原因还是在于方便程序员更加关注业务本身还不是底层细节,对程序员来说理解操作系统的内存架构,CPU指令优化,JIT编译器优化是比较困难的一件事。

### 变量的可见性问题

在多核的服务器时代CPU一般都会拥有多级cache,为了提升其处理性能,比如在上篇文章提到过的L1,L2,L3级cache。这种服务器架构的问题主要在于程序里面的共享变量在横跨多个线程时候的可见性问题。对应到我们写的程序里面就是一个线程写完的变量数据,对于其他线程是否可见。在上面文章中提到过每个线程共享进程的主内存,,同时拥有自己的线程local cache,涉及到变量的读写和可见性问题,其实就是线程的local cache与主内存的数据是否一致的问题。在一个多线程累加同一个变量的程序里面,如果一个线程更新了自己local cache的数据,那么必须在更新完把local cache的数据flush到主内存,否则其他线程读取到的数据就有可能是错误的,另一方面其他线程知道主内存的数据可能会更新,那么就必须放弃自己local cache的数据,直接从主内存加载最新版本的数据用来累加,否则就会出现更新结果不正确的情况。(这部分知识现在理解不了,没关系,后面的文章会慢慢梳理。)

### 关于代码指令的重排序问题

为了方便大家理解重排序的概念,我先举个简单的例子:

```java
public static void main(String []args){

int a=3;
int c=4;

int d=a+c;

System.out.printLn(d);

}
```
上面的代码我们看到a变量是先声明的,c变量是后声明的,但在底层编译,或者JIT优化执行的时候,有可能c变量先被解析,然后才是a变量,这就叫指令重排序,目的是为了提高执行效率,当然指令重排序是有约定的,不管执行顺序如何变动(底层优化导致),在单线程中,它的最终结果必须是和代码顺序执行的结果是一致的。如上面的程序,a和c的位置可以互换,但是和d的顺序是不能变的,这就是它的约定,这个在后面的文章会解释。

那么什么是指令重排序,通俗点讲就是:

_*你看到的代码顺序,不一定是它的执行顺序。*_

上面说了,重排序只保证在单线程程序中,不影响最终结果的前提下允许JIT或者硬件指令做一些优化,但是在多线程程序中重排序是可能会导致一些问题的。

### Java内存模型

重排序和变量可见性问题是多线程编程里面的主要问题,Java内存模型主要描述了下面两种情况的的处理:

(1)重排序是底层编译器优化的结果,所以在Java内存模型里面有一些 happens-before 规则来约束重排序,比如说如果前后两个变量有依赖关系如上面例子中的a和d那么它是不能被重排序的,否则一旦重排序,是会导致程序逻辑错误。

(2)对于共享数据的写操作,是没法通过happens-before关系来约束的,如上面说到的累加的例子,此时需要通过Java里面锁的机制来避免。

如下图:



### 关于同步代码块

同步代码块主要完成了两件事情:

(1)对于共享代码在任何时候只保证有一个线程可以操作,这保证了原子性。

(2)lock和unlock操作会触发当前线程flush自己的cache的数据到主内存中,这保证了可见性的问题。


### 关于volatile关键字

在Java里面用volatie关键字修饰共享变量仅仅只保证可见性,仅仅适用于任何时候只有一个线程更新,多个线程读取的业务。所以如果有超过一个线程以上对变量进行修改,那么必须使用锁机制来处理。

所以请大家记住volatile只保证了可见性,不保证原子性。





### 关于final关键字

在Java里面final关键字修饰的变量,仅仅会被初始化一次,后面是不能修改的。

JIT编译器对final的变量会进行优化,如基本类型String,Int,因为这里不存在修改的问题,那也就没有可见性的问题,所以final修饰基本类型变量在多线程的cache里面的是安全的,不需要和主内存有关联,也就不会有flush或者invalidate的情况。

这也是我们经常说Java里面的String为什么是安全的原因,注意使用final修饰的集合框架如List,Set,Map,虽然内存地址不能变,但是里面的内容是可以变的,这里也是不安全的,这一点需要注意。

这也是为什么有一些函数类型的编程语言如Scala里面严格的提供了不可变的集合框架和可变的集合框架,其目的就是为了更加有利于多线程编程。

最后记住final关键字和volatile关键字是不能修饰同一变量的,在IDE的编译器里面是直接会报错的。



### 总结

如果在阅读之前不了解进程和线程在操作系统里面的关系与特点,我建议你先看看前面的两篇文章再阅读本文。本篇文章主要介绍了Java的内存模型相关内容,如果掌握和熟悉了这些知识,那么对于理解和开发并发编程将是非常有帮助的。



有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs) 路漫漫其修远兮,吾将上下而求索










0
0
分享到:
评论

相关推荐

    Java进阶学习资料.zip

    Java是一种广泛使用的面向对象的编程语言,其设计目标是具有高度的可移植性、健壮性和安全性。...通过2019年的最新学习资料,你可以了解到当时的最佳实践和技术趋势,为你的Java进阶之路提供坚实的基础。

    java进阶篇主要内容的PPT

    **Java进阶篇主要内容概述** 本PPT详细阐述了Java进阶学习的关键概念和技术,旨在帮助读者深化对Java编程的理解,提升开发技能。这个资料涵盖了不仅限于教科书的知识,确保你能够掌握到实际工作中可能遇到的实用...

    java大神进阶之路.pdf

    此外,还包括了JVM内存模型、多线程编程、IO流处理、XML解析、网络编程、数据库设计与操作、Java Web技术栈以及前端技术的学习路径。 【标签】 Java 【知识点详细说明】 **一、编程基础** - 常用数据结构:涉及...

    Java进阶资料

    3. 内存管理与垃圾回收:深入理解Java内存模型,包括堆内存、栈内存、方法区和本地方法栈。了解对象生命周期,探讨GC(Garbage Collection)的工作原理,如新生代、老年代、CMS、G1等垃圾收集器,以及如何优化内存...

    1 Java 程序员进阶之路(暗黑版)1

    5. Java虚拟机(JVM):解析内存模型、垃圾回收、性能优化,帮助开发者更好地理解和调优Java应用程序。 6. Java企业级开发:涵盖Maven项目管理、Git版本控制、SSM(Spring+SpringMVC+MyBatis)框架、Spring Boot快速...

    Java进阶之核心思想共5页.pdf.zip

    【Java进阶之核心思想共5页.pdf】 在Java编程领域,进阶意味着对语言的深入理解和应用。这篇五页的PDF文档很可能涵盖了以下几个关键的Java核心思想: 1. **面向对象编程(OOP)**:Java是面向对象的语言,其核心...

    Java进阶基础.zip

    "Java进阶基础.zip"这个压缩包很可能包含了帮助开发者深化Java理解的材料,涵盖了从核心概念到高级特性的各个方面。以下是一些可能涵盖的Java进阶基础知识: 1. **异常处理**:Java的异常处理机制是其强项之一,...

    Java进阶诀窍

    9. **JVM内存模型**:了解JVM的工作原理,包括类加载机制、内存区域(堆、栈、方法区等)、垃圾回收机制以及调优参数。 10. **设计模式**:学习并掌握常见的设计模式,如工厂模式、单例模式、装饰器模式、观察者...

    Java进阶编程(经典网帖合集)

    6. **JVM优化**:熟悉JVM的工作原理,包括类加载机制、内存模型(堆、栈、方法区等)、垃圾收集器和内存调优,有助于编写出性能更好的程序。 7. **反射**:反射允许在运行时动态地获取类的信息并操作对象,是Java中...

    java高级进阶知识

    1. **内存模型**:JVM内存分为堆内存(Heap)、栈内存(Stack)、方法区(Method Area)、程序计数器(PC Register)和本地方法栈(Native Method Stack)。了解它们各自的作用和交互,能帮助我们理解内存分配和管理...

    Java基础和Java进阶

    本文将详细介绍Java中的JUnit使用流程、集合框架及其进阶、Stream流、多线程、IO流等关键概念。 一、JUnit使用流程 JUnit是一个用于Java编程语言的单元测试框架。在Eclipse中使用JUnit,首先需要编写被测试的代码,...

    Java进阶.zip

    5. **JVM内存模型**:了解堆、栈、方法区、本地方法栈等内存区域的工作方式,以及垃圾回收机制,如分代收集、G1垃圾收集器等。 6. **设计模式**:掌握常见的设计模式,如单例、工厂、观察者、装饰器、代理等,这些...

    Java进阶路线

    ### Java进阶路线详解 #### 一、Java基础 **1. 传值与传引用** 在Java中,基本类型(如int、char等)的传递是按值传递的,而对象类型的传递则是按引用传递的。理解这一点对于正确处理变量和对象之间的交互至关...

    Java进阶教程,面试大全

    Java进阶教程,面试大全1,可参考以下问题: Semaphore-信号灯机制。 synchronized在静态方法和普通方法的区别。 怎么实现所有线程在等待某个事件的发生才会去执行。 CAS。 Hashtable是怎么加锁的。 HashMap的并发...

    java程序设计进阶版

    11. **JVM内部机制**:包括类加载机制、内存模型、垃圾回收、性能优化等方面的知识,对提升程序性能和解决线上问题非常有帮助。 12. **网络编程**:理解TCP/IP协议,使用Socket进行网络通信,以及HTTP、HTTPS协议的...

    JAVA进阶资料 进阶资料

    【Java进阶资料详解】 Java作为一款广泛应用的编程语言,其进阶学习涵盖了多个关键领域。这份资料集合了关于数据库管理、内存管理、服务器配置、性能优化以及消息中间件等多个主题,旨在帮助开发者深入理解Java技术...

    Java进阶教程,面试大全,包罗万象

    Java进阶教程,面试大全1,可参考以下问题: Semaphore-信号灯机制。 synchronized在静态方法和普通方法的区别。 怎么实现所有线程在等待某个事件的发生才会去执行。 CAS。 Hashtable是怎么加锁的。 HashMap的并发...

    Java 进阶之路,互联网一线大厂面试真题解析、经验分享.zip

    3. **JVM深入理解**:理解Java虚拟机(JVM)的工作原理,包括类加载机制、内存模型(堆、栈、方法区)、垃圾收集(GC)机制,以及性能优化策略,这些都是面试中常见的问题。 4. **设计模式**:熟练运用各种设计模式...

    Java语言程序设计进阶篇答案与代码

    理解JVM内存模型有助于优化程序性能和避免内存泄漏。 8. **泛型**:泛型是Java 5引入的特性,用于增强类型安全,减少类型转换的麻烦。泛型类、泛型方法和通配符等概念是其核心内容。 9. **注解(Annotation)**:...

    Java进阶课堂同步代码.zip

    8. **JVM优化**:了解JVM的工作原理,包括内存模型、垃圾回收、类加载机制等,能够帮助我们进行性能调优,提升程序运行效率。 9. **并发编程**:Java并发包(java.util.concurrent)提供了许多高级并发工具,如...

Global site tag (gtag.js) - Google Analytics