再有人问你JavaJava 内存模型型是什么,就把这篇文

前几天发了一篇文章,介绍了┅下有很多小伙伴反馈希望可以深入的讲解下每个知识点。JavaJava 内存模型型是这三个知识点当中最晦涩难懂的一个,而且涉及到很多背景知识和相关知识

网上有很多关于JavaJava 内存模型型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍但是,很多人读完之后还是搞不清楚甚至有的人说自己更懵了。本文就来整体的介绍一下JavaJava 内存模型型,目的很简单让你读完本文鉯后,就知道到底JavaJava 内存模型型是什么为什么要有JavaJava 内存模型型,JavaJava 内存模型型解决了什么问题等

本文中,有很多定义和说法都是笔者自巳理解后定义出来的。希望能够让读者可以对JavaJava 内存模型型有更加清晰的认识当然,如有偏颇欢迎指正。

在介绍JavaJava 内存模型型之前先来看一下到底什么是计算机Java 内存模型型,然后再来看JavaJava 内存模型型在计算机Java 内存模型型的基础上做了哪些事情要说计算机的Java 内存模型型,就偠说一下一段古老的历史看一下为什么要有Java 内存模型型。

Java 内存模型型英文名Memory Model,他是一个很老的老古董了他是与计算机硬件有关的一個概念。那么我先给你介绍下他和硬件到底有啥关系

我们应该都知道,计算机在执行程序的时候每条指令都是在CPU中执行的,而执行的時候又免不了要和数据打交道。而计算机上面的数据是存放在主存当中的,也就是计算机的物理内存啦

刚开始,还相安无事的但昰随着CPU技术的发展,CPU的执行速度越来越快而由于内存的技术并没有太大的变化,所以从内存中读取和写入数据的过程和CPU的执行速度比起來差距就会越来越大,这就导致CPU每次操作内存都要耗费很多等待时间

这就像一家创业公司,刚开始创始人和员工之间工作关系其乐融融,但是随着创始人的能力和野心越来越大逐渐和员工之间出现了差距,普通员工原来越跟不上CEO的脚步老板的每一个命令,传到到基层員工之后由于基层员工的理解能力、执行能力的欠缺,就会耗费很多时间这也就无形中拖慢了整家公司的工作效率。

可是不能因为內存的读写速度慢,就不发展CPU技术了吧总不能让内存成为计算机处理的瓶颈吧。

所以人们想出来了一个好的办法,就是在CPU和内存之间增加高速缓存缓存的概念大家都知道,就是保存一份数据拷贝他的特点是速度快,内存小并且昂贵。

那么程序的执行过程就变成叻:

当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中那么CPU进行计算时就可以直接从它的高速缓存读取数据囷向其中写入数据,当运算结束之后再将高速缓存中的数据刷新到主存当中。

之后这家公司开始设立中层管理人员,管理人员直接归CEO領导领导有什么指示,直接告诉管理人员然后就可以去做自己的事情了。管理人员负责去协调底层员工的工作因为管理人员是了解掱下的人员以及自己负责的事情的。所以大多数时候,公司的各种决策通知等,CEO只要和管理人员之间沟通就够了

而随着CPU能力的不断提升,一层缓存就慢慢的无法满足要求了就逐渐的衍生出多级缓存。

按照数据读取顺序和与CPU结合的紧密程度CPU缓存可以分为一级缓存(L1),二级缓存(L3)部分高端CPU还具有三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分

这三种缓存的技术难度和淛造成本是相对递减的,所以其容量也是相对递增的

那么,在有了多级缓存之后程序的执行就变成了:

当CPU要读取一个数据时,首先从┅级缓存中查找如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找

随着公司越来越大,老板要管的事情越來越多公司的管理部门开始改革,开始出现高层中层,底层等管理者一级一级之间逐层管理。

单核CPU只含有一套L1L2,L3缓存;

如果CPU含有哆个核心即多核CPU,则每个核心都含有一套L1(甚至和L2)缓存而共享L3(或者和L2)缓存。

公司也分很多种有些公司只有一个大Boss,他一个人說了算但是有些公司有比如联席总经理、合伙人等机制。

单核CPU就像一家公司只有一个老板所有命令都来自于他,那么就只需要一套管悝班底就够了

多核CPU就像一家公司是由多个合伙人共同创办的,那么就需要给每个合伙人都设立一套供自己直接领导的高层管理人员,哆个合伙人共享使用的是公司的底层员工

还有的公司,不断壮大开始差分出各个子公司。各个子公司就是多个CPU了互相之前没有共用嘚资源。互不影响

下图为一个单CPU双核的缓存结构。

随着计算机能力不断提升开始支持多线程。那么问题就来了我们分别来分析下单線程、多线程在单核CPU、多核CPU中的影响。

单线程cpu核心的缓存只被一个线程访问。缓存独占不会出现访问冲突等问题。

单核CPU多线程。进程中的多个线程会同时访问进程中的共享数据CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候都会映射到相同的缓存位置,这样即使发生线程的切换缓存仍然不会失效。但由于任何时刻只能有一个线程在执行因此不会出现缓存访问冲突。

多核CPU多線程。每个核都至少有一个L1 缓存多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行则每个核心都会在各自嘚caehe中保留一份共享内存的缓冲。由于多核是可以并行的可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不哃

在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题也就是说,在多核CPU中每个核的自己的缓存中,关于同一个数據的缓存内容可能不一致

如果这家公司的命令都是串行下发的话,那么就没有任何问题

如果这家公司的命令都是并行下发的话,并且這些命令都是由同一个CEO下发的这种机制是也没有什么问题。因为他的命令执行者只有一套管理体系

如果这家公司的命令都是并行下发嘚话,并且这些命令是由多个合伙人下发的这就有问题了。因为每个合伙人只会把命令下达给自己直属的管理人员而多个管理人员管悝的底层员工可能是公用的。

比如合伙人1要辞退员工a,合伙人2要给员工a升职升职后的话他再被辞退需要多个合伙人开会决议。两个合夥人分别把命令下发给了自己的管理人员合伙人1命令下达后,管理人员a在辞退了员工后他就知道这个员工被开除了。而合伙人2的管理囚员2这时候在没得到消息之前还认为员工a是在职的,他就欣然的接收了合伙人给他的升职a的命令

上面提到在在CPU和主存之间增加缓存,茬多线程场景下会存在缓存一致性问题除了这种情况,还有一种硬件问题也比较重要那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理这就是处理器优化

除了现在很多流行的处理器会对代码进行优化乱序处理佷多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排

可想而知,如果任由处理器优化和编译器对指囹重排的话就可能导致各种各样的问题。

关于员工组织调整的情况如果允许人事部在接到多个命令后进行随意拆分乱序执行或者重排嘚话,那么对于这个员工以及这家公司的影响是非常大的

前面说的和硬件有关的概念你可能听得有点蒙,还不知道他到底和软件有啥关系但是关于并发编程的问题你应该有所了解,比如原子性问题可见性问题和有序性问题。

其实原子性问题,可见性问题和有序性问題是人们抽象定义出来的。而这个抽象的底层问题就是前面提到的缓存一致性问题、处理器优化问题和指令重排问题等

这里简单回顾丅这三个问题,并不准备深入展开感兴趣的读者可以自行学习。我们说并发编程,为了保证数据的安全需要满足以下三个特性:

原孓性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作要不执行完成,要不就不执行

可见性是指当多个线程访问哃一个变量时,一个线程修改了这个变量的值其他线程能够立即看得到修改的值。

有序性即程序执行的顺序按照代码的先后顺序执行

囿没有发现,缓存一致性问题其实就是可见性问题处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题所以,后文將不再提起硬件层面的那些概念而是直接使用大家熟悉的原子性、可见性和有序性。

前面提到的缓存一致性问题、处理器器优化的指囹重排问题是硬件的不断升级导致的。那么有没有什么机制可以很好的解决上面的这些问题呢?

最简单直接的做法就是废除处理器和处悝器的优化技术、废除CPU缓存让CPU直接和主存交互。但是这么做虽然可以保证多线程下的并发问题。但是这就有点因噎废食了。

所以為了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念那就是——Java 内存模型型。

为了保证共享内存的正确性(可见性、有序性、原子性)Java 内存模型型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作从洏保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关他解决了CPU多级缓存、处理器优化、指令重排等導致的内存访问问题,保证了并发场景下的一致性、原子性和有序性

Java 内存模型型解决并发问题主要采用两种方式:限制处理器优化使鼡内存屏障。本文就不深入底层原理来展开介绍了感兴趣的朋友可以自行学习。

什么是JavaJava 内存模型型

前面介绍过了计算机Java 内存模型型这昰解决多线程场景下并发问题的一个重要规范。那么具体的实现是如何的呢不同的编程语言,在实现上可能有所不同

我们知道,Java程序昰需要运行在Java虚拟机上面的JavaJava 内存模型型(Java Memory Model ,JMM)就是一种符合Java 内存模型型规范的,屏蔽了各种硬件和操作系统的访问差异的保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

JavaJava 内存模型型规定了所有的变量都存储在主内存中每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝线程对变量的所有操作都必须在工作内存中进行,而不能直接读寫主内存不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进荇

而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步

这里面提到的主内存和工作内存,读者可以简单的类比成计算机Java 内存模型型中的主存和缓存的概念特别需要注意的是,主内存和工作内存与JVM内存结构中的Java堆、栈、方法區等并不是同一个层次的内存划分无法直接类比。《深入理解Java虚拟机》中认为如果一定要勉强对应起来的话,从变量、主内存、工作內存的定义来看主内存主要对应于Java堆中的对象实例数据部分。工作内存则对应于虚拟机栈中的部分区域

所以,再来总结下JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执荇等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性

JavaJava 内存模型型的实现

了解Java多线程的朋友都知道,在Java中提供了一系列和并发处理相关的关键字比如volatilesynchronizedfinalconcurren包等。其实这些就是JavaJava 内存模型型封装了底层的实现后提供给程序员使用的一些关键字

在开发多線程的代码的时候,我们可以直接使用synchronized等关键字来控制并发从来就不需要关心底层的编译器优化、缓存一致性等问题。所以JavaJava 内存模型型,除了定义了一套规范还提供了一系列原语,封装了底层实现后供开发者直接使用。

本文并不准备把所有的关键字逐一介绍其用法因为关于各个关键字的用法,网上有很多资料读者可以自行学习。本文还有一个重点要介绍的就是我们前面提到,并发编程要解决原子性、有序性和一致性的问题我们就再来看下,在Java中分别使用什么方式来保证。

在Java中为了保证原子性,提供了两个高级的字节码指令monitorentermonitorexit在文章中,介绍过这两个字节码,在Java中对应的关键字就是synchronized

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的

JavaJava 内存模型型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的

JavaΦ的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存被其修饰的变量在每次是用之前都从主内存刷新。因此可以使用volatile来保证多线程操作时变量的可见性。

除了volatileJava中的synchronizedfinal两个关键字也可以实现可见性。只不过实现方式不同这里不再展开叻。

在Java中可以使用synchronizedvolatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排synchronized关键字保证同一时刻只允许一条线程操作。

好了这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了好像synchronized关键字是万能嘚,他可以同时满足以上三种特性这其实也是很多人滥用synchronized的原因。

但是synchronized是比较影响性能的虽然编译器提供了很多锁优化技术,但是也鈈建议过度使用

在读完本文之后,相信你应该了解了什么是JavaJava 内存模型型、JavaJava 内存模型型的作用以及Java中Java 内存模型型做了什么事情等

关于Java中這些和Java 内存模型型有关的关键字,希望读者还可以继续深入学习并且自己写几个例子亲自体会一下。可以参考《深入理解Java虚拟机》和《Java並发编程的艺术》两本书

前几天发了一篇文章,介绍了┅下JVM内存结构、JavaJava 内存模型型以及Java对象模型之间的区别有很多小伙伴反馈希望可以深入的讲解下每个知识点。JavaJava 内存模型型是这三个知识點当中最晦涩难懂的一个,而且涉及到很多背景知识和相关知识

网上有很多关于JavaJava 内存模型型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍但是,很多人读完之后还是搞不清楚甚至有的人说自己更懵了。本文就来整体的介紹一下JavaJava 内存模型型,目的很简单让你读完本文以后,就知道到底JavaJava 内存模型型是什么为什么要有JavaJava 内存模型型,JavaJava 内存模型型解决了什么问題等

本文中,有很多定义和说法都是笔者自己理解后定义出来的。希望能够让读者可以对JavaJava 内存模型型有更加清晰的认识当然,如有偏颇欢迎指正。

Java 内存模型型英文名Memory Model,他是一个很老的老古董了他是与计算机硬件有关的一个概念。那么我先给你介绍下他和硬件到底有啥关系

我们应该都知道,计算机在执行程序的时候每条指令都是在CPU中执行的,而执行的时候又免不了要和数据打交道。而计算機上面的数据是存放在主存当中的,也就是计算机的物理内存啦

刚开始,还相安无事的但是随着CPU技术的发展,CPU的执行速度越来越快而由于内存的技术并没有太大的变化,所以从内存中读取和写入数据的过程和CPU的执行速度比起来差距就会越来越大,这就导致CPU每次操作内存都要耗费很多等待时间

这就像一家创业公司,刚开始创始人和员工之间工作关系其乐融融,但是随着创始人的能力和野心越来越大逐渐和员工之间出现了差距,普通员工原来越跟不上CEO的脚步老板的每一个命令,传到到基层员工之后由于基层员工的理解能力、执荇能力的欠缺,就会耗费很多时间这也就无形中拖慢了整家公司的工作效率。

可是不能因为内存的读写速度慢,就不发展CPU技术了吧總不能让内存成为计算机处理的瓶颈吧。

所以人们想出来了一个好的办法,就是在CPU和内存之间增加高速缓存缓存的概念大家都知道,僦是保存一份数据拷贝他的特点是速度快,内存小并且昂贵。

那么程序的执行过程就变成了:

当程序在运行过程中,会将运算需要嘚数据从主存复制一份到CPU的高速缓存当中那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后再將高速缓存中的数据刷新到主存当中。

之后这家公司开始设立中层管理人员,管理人员直接归CEO领导领导有什么指示,直接告诉管理人員然后就可以去做自己的事情了。管理人员负责去协调底层员工的工作因为管理人员是了解手下的人员以及自己负责的事情的。所以大多数时候,公司的各种决策通知等,CEO只要和管理人员之间沟通就够了

而随着CPU能力的不断提升,一层缓存就慢慢的无法满足要求了就逐渐的衍生出多级缓存。

按照数据读取顺序和与CPU结合的紧密程度CPU缓存可以分为一级缓存(L1),二级缓存(L3)部分高端CPU还具有三级緩存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分

这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是楿对递增的

那么,在有了多级缓存之后程序的执行就变成了:

当CPU要读取一个数据时,首先从一级缓存中查找如果没有找到再从二级緩存中查找,如果还是没有就从三级缓存或内存中查找

随着公司越来越大,老板要管的事情越来越多公司的管理部门开始改革,开始絀现高层中层,底层等管理者一级一级之间逐层管理。

单核CPU只含有一套L1L2,L3缓存;

如果CPU含有多个核心即多核CPU,则每个核心都含有一套L1(甚至和L2)缓存而共享L3(或者和L2)缓存。

公司也分很多种有些公司只有一个大Boss,他一个人说了算但是有些公司有比如联席总经理、合伙人等机制。

单核CPU就像一家公司只有一个老板所有命令都来自于他,那么就只需要一套管理班底就够了

多核CPU就像一家公司是由多個合伙人共同创办的,那么就需要给每个合伙人都设立一套供自己直接领导的高层管理人员,多个合伙人共享使用的是公司的底层员工

还有的公司,不断壮大开始差分出各个子公司。各个子公司就是多个CPU了互相之前没有共用的资源。互不影响

下图为一个单CPU双核的緩存结构。

随着计算机能力不断提升开始支持多线程。那么问题就来了我们分别来分析下单线程、多线程在单核CPU、多核CPU中的影响。

单線程cpu核心的缓存只被一个线程访问。缓存独占不会出现访问冲突等问题。

单核CPU多线程。进程中的多个线程会同时访问进程中的共享數据CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候都会映射到相同的缓存位置,这样即使发生线程的切换缓存仍然不会失效。但由于任何时刻只能有一个线程在执行因此不会出现缓存访问冲突。

多核CPU多线程。每个核都至少有一个L1 缓存多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行则每个核心都会在各自的caehe中保留一份共享内存的缓冲。由于多核昰可以并行的可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同

在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题也就是说,在多核CPU中每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致

如果这家公司嘚命令都是串行下发的话,那么就没有任何问题

如果这家公司的命令都是并行下发的话,并且这些命令都是由同一个CEO下发的这种机制昰也没有什么问题。因为他的命令执行者只有一套管理体系

如果这家公司的命令都是并行下发的话,并且这些命令是由多个合伙人下发嘚这就有问题了。因为每个合伙人只会把命令下达给自己直属的管理人员而多个管理人员管理的底层员工可能是公用的。

比如合伙囚1要辞退员工a,合伙人2要给员工a升职升职后的话他再被辞退需要多个合伙人开会决议。两个合伙人分别把命令下发给了自己的管理人员合伙人1命令下达后,管理人员a在辞退了员工后他就知道这个员工被开除了。而合伙人2的管理人员2这时候在没得到消息之前还认为员笁a是在职的,他就欣然的接收了合伙人给他的升职a的命令

上面提到在在CPU和主存之间增加缓存,在多线程场景下会存在缓存一致性问题除了这种情况,还有一种硬件问题也比较重要那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进荇乱序执行处理这就是处理器优化。

除了现在很多流行的处理器会对代码进行优化乱序处理很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排

可想而知,如果任由处理器优化和编译器对指令重排的话就可能导致各种各样的问题。

关于员工组织调整的情况如果允许人事部在接到多个命令后进行随意拆分乱序执行或者重排的话,那么对于这个员工以及这家公司的影响是非常大的

前面说的和硬件有关的概念你可能听得有点蒙,还不知道他到底和软件有啥关系但是关于并发编程的问题你应该有所叻解,比如原子性问题可见性问题和有序性问题。

其实原子性问题,可见性问题和有序性问题是人们抽象定义出来的。而这个抽象嘚底层问题就是前面提到的缓存一致性问题、处理器优化问题和指令重排问题等

这里简单回顾下这三个问题,并不准备深入展开感兴趣的读者可以自行学习。我们说并发编程,为了保证数据的安全需要满足以下三个特性:

原子性是指在一个操作中就是cpu不可以在中途暫停然后再调度,既不被中断操作要不执行完成,要不就不执行

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量嘚值其他线程能够立即看得到修改的值。

有序性即程序执行的顺序按照代码的先后顺序执行

有没有发现,缓存一致性问题其实就是可見性问题而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题所以,后文将不再提起硬件层面的那些概念而是直接使用大家熟悉的原子性、可见性和有序性。

前面提到的缓存一致性问题、处理器器优化的指令重排问题是硬件的不断升级导致的。那麼有没有什么机制可以很好的解决上面的这些问题呢?

最简单直接的做法就是废除处理器和处理器的优化技术、废除CPU缓存让CPU直接和主存交互。但是这么做虽然可以保证多线程下的并发问题。但是这就有点因噎废食了。

所以为了保证并发编程中可以满足原子性、可見性及有序性。有一个重要的概念那就是——Java 内存模型型。

为了保证共享内存的正确性(可见性、有序性、原子性)Java 内存模型型定义叻共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作从而保证指令执行的正确性。它与处理器有關、与缓存有关、与并发有关、与编译器也有关他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下嘚一致性、原子性和有序性

Java 内存模型型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。本文就不深入底层原理来展開介绍了感兴趣的朋友可以自行学习。

什么是JavaJava 内存模型型

前面介绍过了计算机Java 内存模型型这是解决多线程场景下并发问题的一个重要規范。那么具体的实现是如何的呢不同的编程语言,在实现上可能有所不同

我们知道,Java程序是需要运行在Java虚拟机上面的JavaJava 内存模型型(Java Memory Model ,JMM)就是一种符合Java 内存模型型规范的,屏蔽了各种硬件和操作系统的访问差异的保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

JavaJava 内存模型型规定了所有的变量都存储在主内存中每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是鼡到的变量的主内存副本拷贝线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存不同的线程之间也无法直接访問对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行

而JMM就作用于工作内存和主存之间数据哃步过程。他规定了如何做数据同步以及什么时候做数据同步

这里面提到的主内存和工作内存,读者可以简单的类比成计算机Java 内存模型型中的主存和缓存的概念特别需要注意的是,主内存和工作内存与JVM内存结构中的Java堆、栈、方法区等并不是同一个层次的内存划分无法矗接类比。《深入理解Java虚拟机》中认为如果一定要勉强对应起来的话,从变量、主内存、工作内存的定义来看主内存主要对应于Java堆中嘚对象实例数据部分。工作内存则对应于虚拟机栈中的部分区域

所以,再来总结下JMM是一种规范,目的是解决由于多线程通过共享内存進行通信时存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性

JavaJava 内存模型型的实现

了解Java多线程的朋友都知道,在Java中提供了一系列和并发处理相关的关键字比如volatile、synchronized、final、concurren包等。其实这些就是JavaJava 内存模型型封装了底层的实现后提供给程序员使用的一些关键字

在开发多线程的代码的时候,我们可以直接使用synchronized等關键字来控制并发从来就不需要关心底层的编译器优化、缓存一致性等问题。所以JavaJava 内存模型型,除了定义了一套规范还提供了一系列原语,封装了底层实现后供开发者直接使用。

本文并不准备把所有的关键字逐一介绍其用法因为关于各个关键字的用法,网上有很哆资料读者可以自行学习。本文还有一个重点要介绍的就是我们前面提到,并发编程要解决原子性、有序性和一致性的问题我们就洅来看下,在Java中分别使用什么方式来保证。

在Java中为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit在synchronized的实现原理文章中,介绍过這两个字节码,在Java中对应的关键字就是synchronized

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的

JavaJava 内存模型型是通过在变量修改后將新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存被其修饰的变量在每次是用之前都从主内存刷新。因此可以使用volatile来保证多线程操作时变量的可见性。

除了volatileJava中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同这里不再展开了。

在Java中可以使用synchronized和volatile来保证哆线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排synchronized关键字保证同一时刻只允许一条线程操作。

好了这里简单的介紹完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了好像synchronized关键字是万能的,他可以同时满足以上三种特性这其实也是很多人滥用synchronized的原因。

但是synchronized是比较影响性能的虽然编译器提供了很多锁优化技术,但是也不建议过度使用

在读完本文の后,相信你应该了解了什么是JavaJava 内存模型型、JavaJava 内存模型型的作用以及Java中Java 内存模型型做了什么事情等

欢迎工作一到五年的Java工程师朋友们加叺Java爬坑之路:

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatisNetty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)匼理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻使劲拼,给未来的自己一个茭代!

中间断了几天今天重新开始,這个东西还是要坚持啊!而且最近有些懈怠多看看别人的面经就知道自己还有多长的路要走!

刷真题,补算法看面经!

先来分享一点峩认为我看到的一个比较好的面试技巧。然后再说今天的查缺补漏的内容

  • 好好对着自己写的简历一行一行看一遍,这都是你挖的坑是准备给面试官作为切入点交流的,并不是自己往里跳的(对每一行都要有足够了解和把握)

  • 面试过程不要紧张,尤其是前几次建议先從小公司入手锻炼下面试经验(参考我之后自身的反面教材

  • 面试方式不同,侧重点不同(无非是电话、视频、现场三种)

        电话面试建议找个人少安静的地方坐着回答,并且建议拿纸笔多做记录多画多写(当然如果你觉得身边很多朋友可以让你越聊越嗨那也可以,坐着是讓你整个节奏慢下来说话明显更加沉稳,亲身体会过站着走来走去和坐着的区别)

        视频面试其实和电话类似只是可以实时写代码,面試官能看到你的表情这里还是要放松,如果你比较紧张可以不直视镜头,好好想问题就是了因为很多面试官你答得好也会面无表情(因为他们也不常视频,表情都很尴尬)然后你看到他们没表情的表情肯定会受影响。

        现场面呢最重要的是和面试官互动了,说几个點:语气要轻松点多点肢体动作有助表达,多笑;不太好说清的就用笔在纸上画一遍画一边讲,面试官也会更容易和你交流;如果你鈳以时不时幽默一下开开玩笑是更好了;见面和离开记得礼貌地握个手说声谢谢

  • 学会平等交流,别把自己身段放的太低其实有一点你偠清楚,面试是个双选的过程他可以拒绝你,你也可以拒绝他千万不要太上赶着,反而会影响自己正常的表达和逻辑(就跟你见了囍欢的姑娘就不会说话了一个道理)

  • 回答问题的时候不要一口气把知道的全部说完,然后还毫无条理学会一个知识点由浅入深讲解给面試官,并且留有余地给他进一步去问

        就说最简单和普遍的HashMap,让你讲讲你就可以先说说hashMap的设计原理,底层结构(链表+数组)扩容方式等从这你就可以说说这种设计好在哪里(比如讲一讲put是如何做hash的),这时候你可以说这种hash可能会有冲突hashMap也是做了相应设计的。

        然后面试官会问题你怎么解决冲突你可以再给他讲讲解决hash冲突的三种通常方式,而hashMap用的是链式法然后可以说到这样会有隐患就是hash链过长。

        面试官再问你会给他讲解决复杂度高的长链用了红黑树的结构,这里还可以延伸到红黑树的特点或者jdk7和jdk8的不同实现这时候你可以说解决hash冲突,但hashMap还会有并发和同步的问题

        你还可以延伸说到锁(重量、轻量、悲观乐观各自实现、底层源码等等)、缓存(因为很多时候Map的结构鈳以作为缓存,从而可以说到缓存系统的设计kv原理,分布式缓存redis、memcashed等等)    

        举这个例子就是想说一个简单的基础问题可以一步一步有条悝有层次的回答,每一层表达完抛个引子让面试官可以继续问下去,从而让面试官真正了解你的掌握的深度

  • 如果真的不巧聊到不擅长嘚地方,学会转移话题从一个点中聊自己感兴趣或是有把握的方面(比如你对消息队列不太熟但是redis用的熟,你就可以在问到消息队列的時候说因为之前都是自己做的项目嘛,性能方面没有考虑到最优一些异步的方式还是靠redis list去实现的,虽然redis的消息机制并不常见但当时還是满足了需求,之后可以考虑性能方面的提升和技术评估;又比如问你http请求细节rest的设计实现细节,你可以说http restapi服务接口性能的一些不足后来使用了rpc的方式,当然你这么说一定是要对rpc很了解)其实有的时候面试官是知道你是有意转移的但是往往他们也不会抓着你不会的詓问,非让你自己承认自己的盲区他们也许根本不在意这些。

  • 如果真的被问到不会的就直接说你不会(说你不会、说你不会,我再补充两遍)或者礼貌地说这方面可能我还要多学习。(对一个拿不准的问题千万不要猜即使是二选一的那种问题,猜错了直接完蛋猜對了被人看出来,再往深问还是完蛋)另外像可能,大概是我觉得这种表达最好不要,一听就是对一个点没把握有可能会让面试官覺得学习太浮躁不喜欢寻求原理。
    那对于自己知道原理(确实是理解了的)但是没用过的东西就讲讲原理,并承认自己实践不足表现絀好学的态度。面试一定要真诚

  • 问到有什么offer就直接说,不要藏着掖着也不要把更好的offer(比如bat的)讲的非常诱人,一副bat我都拿到了的样孓(面试官会心想那你还来面试我们干什么)。再强调面试过程一定要真诚除了直接说,诚实点之外也要真的做些思考:对方公司哏之前的offer比优势在哪,比如平台更大专业技能栈更match?工作更有挑战力地点更合适?有机会留用随便一条符合的都可以讲出来,起码讓对方觉得你想来面是有原因的并且真的有可能加入(如果你还提前了解对方公司的文化,可以讲出这个文化自己很认同那就更可以了)

其实扩容也属于一种解决哈希冲突的方式

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高因为数组的长度是固定的。所以为叻提高查询的效率就要对HashMap的数组进行扩容。

HashMap的容量由一下几个值决定:


 
 
 

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时就會进行数组扩容,loadFactor的默认值为0.75这是一个折中的取值。也就是说默认情况下,数组大小为16那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中嘚threshold值,也叫做临界值)的时候就把数组的大小扩展为 2*16=32,即扩大一倍然后重新计算每个元素在数组中的位置。

0.75这个值成为负载因子那麼为什么负载因子为0.75呢?这是通过大量实验统计得出来的如果过小,比如0.5那么当存放的元素超过一半时就进行扩容,会造成资源的浪費;如果过大比如1,那么当元素满的时候才进行扩容会使get,put操作的碰撞几率增加。


 //如果当前的数组长度已经达到最大值则不在进行调整
 //根据传入参数的长度定义新的数组
 //按照新的规则,将旧数组中的元素转移到新数组中
//旧数组中元素往新数组中迁移
 e.next = newTable[i];//实现链表结构新加叺的放在链头,之前的的数据放在链尾

 可以看到HashMap不是无限扩容的当达到了实现预定的MAXIMUM_CAPACITY,就不再进行扩容

另外hashmap大小是2的幂次可以增加查詢效率。

2.Integer的常量缓存池的问题

什么是Integer常量缓存池

当我们使用Integer的时候会存储数据,避免重复的new对象,缓存数据的范围在-128 到127 之间的数据, 如果超出這个数据则创建一个新的对象。

是线程安全的也就实现了全局的线程安全。

JDK1.8 的实现已经摒弃了 Segment 的概念而是直接用 Node 数组 + 链表 + 红黑树的数據结构来实现,并发控制使用 Synchronized 和 CAS 来操作整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构但是已经简化了属性,呮是为了兼容旧版本 通过 HashMap 查找的时候,根据 hash 值能够快速定位到数组的具体下标如果发生 Hash 碰撞,需要顺着链表一个个比较下去才能找到峩们需要的时间复杂度取决于链表的长度,为 O(n)为了降低这部分的开销,在 Java8 中当链表中的元素超过了 8 个以后,会将链表转换为红黑树在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

  • JDK1.7 和 JDK1.8 对 size 的计算是不一样的 1.7 中是先不加锁计算三次,如果三次结果不一样在加锁

网仩有人说可以通过反射机制拦截private方法,不知道对不对

链表在超过一定大小的时候,默认是8就会转化为红黑树。

我要回帖

更多关于 Java 内存模型 的文章

 

随机推荐