查看原文
其他

并发编程JMM系列之基础!

Justin的后端书架 Java后端技术 2019-01-08

来源 | 公众号 |  Justin的后端书架

Java程序员在进行多线程开发时,并不需要关心线程间是如何通信的,这些对程序员本来来说完全是透明的,但是内存可见性问题很容易让我们困惑,今天我们就讲讲Java内存模型(JMM)相关知识点,首先我们先讲讲内存模型的一些基本概念,对内存模型有个大概的认识,让我们开始今天的并发之旅吧。

一、并发编程模型的关键问题

并发编程模型主要有两种模型:共享内存模型和消息传递模型,在这两种模型中都需要解决并发编程最关键的两个问题:

共享内存模型:

  • 线程之间通信:线程之间共享程序的公共状态,通过读-写内存中的公共状态,进行隐式通信;

  • 线程之间同步:程序员必须显式的指定某个方法或者代码块需要在线程间互斥执行,显式进行同步;

消息传递模型:

  • 线程之间通信:线程之间通过发送消息,进行显式通信;

  • 线程之间同步:消息的发送必须在消息的接收之前,隐式进行同步;

通信指的是线程之间以什么机制来交换信息;

同步指的是程序用于控制不同线程间操作发生相对顺序的机制;

Java的并发采用的是共享内存模型,Java之间线程通信是隐式进行的,因此我们要理解隐式进行的线程间通信机制,才能更好的掌握Java并发,下面我们就谈谈这种隐式的通信机制。

二、Java内存模型的抽象结构

在Java中,所有的实例域,静态域和数组元素都存储在堆内存中,堆内存在线程间是共享的,因此这些数据对象会受到内存模型的影响;

Java线程之间的通信就依赖于Java内存模型(JMM),JMM决定了一个线程对共享变量的写入何时对另一个线程可见;从抽象角度看,JMM定义了线程和主内存之间的抽象关系,如下图所示:

JMM抽象模型:线程之间的共享变量存在主内存中,每个线程都有一个本地内存,用于存储主内存中共享变量的副本;

JMM线程通信过程:线程A每次把本地内存A中更新过的共享变量刷新到主内存中,线程B到主内存中去读取线程A更新后的共享变量,并刷新到本地内存B中;完成线程AB之间的通信。这个过程一定得经过主内存,所以JMM是通过控制主内存与每个线程的本地内存之间交互来提供内存可见性的保证机制。

三、源代码到指令序列重排序

为了提高程序执行的性能,编译器和处理器常常会对指令做重排序处理,整个过程如下:

四、处理器对内存操作的影响

现在常见的处理器都使用写缓冲区,首先我们看下使用写缓冲区有什么好处?再看看对内存操作有什么影响?

写缓冲区的优势:写缓冲区临时保存线程向内存写入的数据,这样做可以避免处理器停下来等待向内存写入数据产生的延迟,通过批处理的方式刷新写缓冲区,合并写缓冲区中对同一内存地址的多次读写,从而减少了对内存总线的占用,提高了处理器处理数据的效率。

对内存执行顺序的影响处理器对内存读写操作顺序不一定与内存实际发生的读写顺序一致,可能造成数据结果的不一致。

怎么解决这个问题呢?

还是之前提到的内存屏障,Java编译器在生成指令时会在适当位置插入内存屏障指令来禁止特定类型的处理器重排序(执行顺序会影响执行结果的这种),JMM把内存屏障分为下面四种:

五、先行发生原则

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作直接必须要存在先行发生原则(happens-before)。

先行发生原则规则如下:

程序次序规则:同一个线程内,按照程序代码顺序,写在前面的操作先行发生于写在后面的操作;

管程锁定原则:对于一个锁的解锁,先行发生于随后对这个锁的加锁;

volatile变量规则:对一个volatile变量的写操作先行发生于任意后续对该变量的读操作;

线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;

线程终止规则:线程的所有操作都先行发生于对此线程的终止检测,可以通过Thread.join()方法结束和Thread.isAlive()的返回值等手段检测到线程已经终止执行。

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断时事件的发生。Thread.interrupted()可以检测是否有中断发生。

对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()的开始。

传递性:如果操作A 先行发生于操作B,操作B 先行发生于操作C,那么可以得出A 先行发生于操作。

本次送书


编辑推荐:

Java是一门非常流行的程序语言,但是,Java程序到底是如何运行的?如何写出更高效的Java代码……?这些令人困扰的问题,都可以从《揭秘Java虚拟机:JVM设计原理与实现》中找到答案。

随着互联网的极速发展,现在的Java 服务端应用需要应对极高的并发访问和大量的数据交互,如果能深入地理解Java虚拟机的核心原理和实现细节,不仅可以帮助程序员自如地应对变化莫测的各类状况,也可以帮助程序员写出优秀高效的代码。

《揭秘Java虚拟机:JVM设计原理与实现》正是这样一部修炼Java内功的武学秘笈。它不仅详细阐述了JVM的设计思路与原理,让读者能够深刻理解JVM的运行机制,而且更进一步地分析了JVM在发展过程中每一次技术选择的必然性——这样的分析让读者得以跨越时空,在思想上与前辈大师们产生共鸣,实现精神上的交流,真正领悟JVM的精髓。

更为难得的是,看似艰深莫测的“大道理”,却被作者用晓畅明白、幽默有趣的文字层层化解了:《揭秘Java虚拟机:JVM设计原理与实现》在阅读体验上毫无枯燥之感,相反,却能让你在“顿悟”的当下,会心而笑,不忍释卷!

不必犹豫,无须比较,就是它——《揭秘Java虚拟机:JVM设计原理与实现》!


2018年

03月17日

电子工业出版社 | 博文视点

本次活动赞助商

博文视点(Broadview)在IT出版领域打磨多年,以敏锐眼光、独特视角密切关注技术发展趋势及变化,致力于将技术大师之优秀思想、一线专家之一流经验集结成书,为众多爱学习的小伙伴奉献精诚佳作,助力个人、团队成长。

活动规则

为了照顾那些不想评论的小伙伴,这里祭出抽奖二维码,今天活动抽两本,有兴趣的可以扫码参与!注意:中奖的小伙伴,一定要写清楚收货信息,包括收货人姓名、手机号码、地址,否则作为弃权处理!


点击图片查看更多推荐内容

↓↓↓

为什么你创建的数据库索引没有生效?

面试必备:什么是一致性Hash算法?

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题!

为什么MySQL数据库索引选择使用B+树?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存