java线程内存模型底层实现原理

简介: java线程内存模型底层实现原理

一、多核并发缓存架构

在计算机里面有多个cpu和主内存,早期的计算机只有主内存和cpu。cpu要读取数据,而数据一般在硬盘上的,一开始是先把数据读取到主内存,然后再cpu和主内存进行交互,去拿些数据,或者说再和这些数据做些运算。早期的计算机是cpu和主内存直接打交道的。这么多年的发展,cpu的计算速度是非常快的。在摩尔定律里面。cpu每隔18个月左右,它的运算速度会提升很多,但是主内存的读取数据的速度,和存储数据的速度并不大。随着cpu的高速提升,然后cpu和主内存的数据的交换肯定有性能瓶颈的,一直会卡在主内存,为了解决这个问题,然后再他俩之间引入了cpu缓存,cpu寄存器也可以看成是cpu缓存。cpu缓存的速度和cpu的速度是很接近的。读取数据,就是和cpu缓存频繁打交道的。

一、java线程内存模型

1、概念:

Java线程内存模型跟cpu缓存模型类似,是基于cpu缓存模型来建立的,Java线程内存模型是标准化的,屏蔽了底层不同计算机的区别,严格的讲java内存模型是Java线程内存模型

比如在上面的图中,多个线程同时运行程序,如果是多核cpu的话,可能是一个线程利用一个一核cpu,比如一些共享变量是存储到主内存里面的。而线程不会频繁的和主线程做交互,而是把主内存里面的共享变量复制一份到工作内存里面。所以线程运行程序的时候是和工作内存频繁的在做交互,这样性能会很高,同时线程B,C也是这样。

举个例子,代码如下:

  1. public class VolatileVisibility {
  2.    //共享变量
  3.    private  static  boolean  initFlag=false;

  4.    public static void main(String[] args) throws InterruptedException {
  5.        new Thread(new Runnable() {
  6.            public void run() {
  7.                System.out.println("waiting  data.....");
  8.                while(!initFlag){

  9.                }
  10.                System.out.println("-------------------------success");
  11.            }

  12.        }).start();
  13.        Thread.sleep(2000);
  14.        new Thread(new Runnable() {
  15.            public void run() {
  16.                prepareData();
  17.            }
  18.        }).start();
  19.    }
  20.    public  static  void  prepareData(){
  21.        System.out.println("prepareing data.....");
  22.        initFlag=true;
  23.        System.out.println("prepare data end.....");
  24.    }

  25. }

运行的结果如下:第一个线程没有结束

解释:上面的initFlag:也就是上面图中的共享变量。之前是等于false。这两个线程是会同时进行操作这个变量,把这个共享变量分别加载到自己的工作内存从而操作,所以一开始在两个线程的工作内存上都为false,第二个线程把initFlag改为true了。而第一个线程是没有感知的,因为改的是第二个变量副本和主内存中的共享变量,第一个线程没有改自己的变量副本的。

如何去修改上面的死循环呢?

在共享变量上加上一个volatile关键字就可以了:保证多线程在操作共性变量的可见性。这样答问题的话是太入门了,下面会说底层的原理,怎么达到可见性的。

想要把volatile搞明白的话,必须还要把java内存模型搞明白:

二、java内存模型:

java内存模型除了上面的图的以后还定义了一些原子操作,程序的运行是按照上面的图运行走的。

1、java中的线程原子操作:

read:就是把主内存的共享变量读取出来,也就是对应下面的操作

use:线程1操作做的就是取反操作

两个线程,线程1等待数据的操作,线程2准备数据的操作:

1、首先主内存的值为false,随着程序的运行,第一个线程开始执行的时候把主内存的变量加载到工作内存的里面:会经历read操作,把主内存的数据给 读取出来。

2、load把数据变量读取到工作内存里面。

3、use:做的是取反操作

上面的就是线程1的操作。

第二个线程就是把主内存的数据读取后来然后再load下,这时把变量放到工作内存里面取了。

上面的图就是解释没有加volatile关键字的程序之前的效果。

而加了volatile关键字之后早期底层实现:

加了volatile关键字之后,两个线程之间的工作内存的副本变量就是可见的,就是达到同步数据的效果,早期的硬件级别使用的是总线加锁的效果。

总线:学过计算机组成原理的都听说过总线的,比如说主内存和工作内存是通过总线来操作的,cpu和主内存交互,在物理硬件中是cpu在一个地方,主内存在另一个地方。两个是通过一排一排的线连接的。就可以把这个线当作成总线。数据的传输是通过总线来传输的。早期的volatile也解决了共享变量一致性的问题,也就是没有加锁之前这两个线程是并行在执行的。

加锁之后就把并行的操作变成串行的操作了。

A、总线加锁(性能太低)

cpu从主主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他cpu没法去读或写这个数据,直到这个cpu使用完成数据释放锁之后其他cpu才能读取该数据。

下面的图就是早期的volatile关键字的底层情况,底层实现并不一定是代码实现的,而是硬件实现的。

在上面的图中,早期的操作是在read的过程之前做一个lock的操作,其他的线程通过总线拿主内存中的数据的话,这时发现数据已经加上一把锁了,这时是拿不到数据的,这时的锁可能为写锁啊,读锁啊,暂时先不管,直到在线程2所有的操作执行完执行unlock操作释放锁之后,其他的线程才可以再来拿数据,而其他线程可能涉及到锁的争抢。而拿到值的线程最先肯定要加锁。最新拿到锁的线程就能够拿到新的值了,其他的线程就需要等待。这就可以解决早期的共享变量的一致性的问题。

没有加锁之前,两个线程是完全并行再执行的。但是加完锁之后,可能一开始还是并行在执行的,但是这两个线程读取到同一个变量的时候,他们之间可能就争抢锁。争抢锁可能就需要排队。把完全并行的操作就变成串行的操作了。

这种早期总线加锁的效率降低。这种很多时候不是java代码实现的,而是硬件去实现的。

而现在java底层对volatile关键字是怎么实现的呢?

先说下MESI缓存一致性协议

volatile的底层实现就是借助于MESI缓存一致性协议实现的

B、MESI缓存一致性协议:

多个cpu从主内存读取同一个数据到各自的告诉缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据的变化从而将自己的缓存里的数据失效。

volatile底层的实现大概就是这么操作。

在多线程执行的时候会把共享变量放入到自己的工作内存,当其中的某个cpu修改了缓存中的副本变量的值,如果线程2把这个值给修改了,这个线程只要把这个值同步到主内存的话,其实store的操作就是把这个值写入到主内存了,然后通过write操作写入到主内存的对象里面去。当cpu启动MESI缓存一致性协议被启动之后,当线程2通过store的操作的时候,通过总线的一刹那。然后线程1的启动cpu总线嗅探机制就会感知总线的关键字很敏感;initFlag,并且把自己本身的工作内存的原本的值给失效。然后线程1所在的cpu发现这个变量的地址已经失效了。然后线程1重新去主内存read操作和load操作,这时true取反跳出了while的循环,这就把可见性的问题给解决了。线程之间无法进行数据传输,必须通过主内存。这就是通过MESI缓存一致性协议来解决共享变量可见性的问题。

上面的就是volatile的底层的一个最简单的实现,volatile关键字借助了MESI缓存一致性协议实现了,其实还是借助更多的来实现的,比如加锁的实现:底层是用c语言来实现的。

下面提出两个问题:

1、如果两个子线程同时回写主内存的话,是个问题,

2、如果其中一个线程没有同步到主内存的时候,另一个线程的cpu总线嗅探机制已经监听到initFlag的值有变化之后,然后把自己的工作内存中的initFlag失效。这时线程1读的数据还是false,这又是一个问题。所以并发编程不是那么简单的

由下篇文章揭晓答案,明天继续去写深入汇编语言来分析volatile关键字。

相关文章
|
16天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
7天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
37 6
|
20天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
19天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
31 8
|
15天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
15天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
40 3
|
16天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
16天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
20天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
46 5
|
18天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
下一篇
DataWorks