JAVA内存模型与线程

时间:2022-04-17 06:02:55

概述

  由于计算机的运算速度和它的存储和通讯子系统的速度差距巨大,大部分时间都花在IO,网络和数据库上。为了压榨CPU的运算能力,需要并发。另外,优秀的并发程序对于提高服务器的TPS有重要的意义。

硬件的效率和一致性

  由于运算速度的差距,CPU和存储设备间加入多层的cache。同时也引入了缓存一致性的问题。解决缓存一致性有多种读写协议,(MSI,MESI,MOSI,Synapse,Firefly和Dragon Protocol等。

JAVA内存模型与线程

  为了使的处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结构重组,保证结果与顺序执行的结果是一致的,但不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。,因此存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序不能依靠代码的先后顺序来保证。与处理器的乱序执行优化类似,java虚拟机的即时编译器中也有类似的指令重排序优化。

Java内存模型

  Java内存模型用来屏蔽掉各种硬件和操作系统的内存访问差异,已让java程序在各种平台都能达到一致的内存访问效果。Java内存模型的目标是定义程序中各个变量的刚问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

主内存与工作内存

  Java内存模型规定了变量(不含局部变量,因为局部变量线程私有,不存在共享问题)都得存放在主内存中,而每个线程对这些变量的操作都必须是从主内存中取出来并在工作内存中完成(如读取、写入的操作),不同线程之间不能访问对方的工作内存。如下图,展现了线程、主内存、工作内存之间的交互关系:

JAVA内存模型与线程
  加入一个工作内存的目的很明显,就是为了加快在内存中的操作数据的速度,因为工作内存优先存储在寄存器和高速缓存中,这两个操作的速度都远远快于主内存。主内存和工作内存之间交互的主要操作为:

  • Lock(锁定):作用于主内存的变量,将一个变量标示为一条线程独占的状态,其他线程不能访问。
  • Unlock(解锁):作用于主内存的变量,将一个处于锁定状态的变量释放出来,释放后的变量可以被其他线程锁定。
  • Read(读取):作用于主内存的变量,将一个变量的值从主内存传输到线程的工作内存,,以便进行load操作。
  • Load(加载):作用于工作内存中的变量,将read获取到的变量载入工作内存的变量副本中。
  • Use(使用):作用于工作内存中的变量,虚拟机执行引擎在执行字节码指令的时候,碰到了一个变量就会执行该操作,使用该变量。
  • Assgin(赋值):作用于工作内存中的变量,虚拟机执行引擎在执行字节码指令的时候,碰到了变量赋值的指令就会执行该操作。
  • Store(存储):作用于工作内存中的变量,将工作内存中的变量放入主内存,以便进行write操作。
  • Write(写入):作用于主内存中的变量,将store得到的变量放入主内存的变量中。

  JAva内存模型还规定了在执行上述8中操作时必须满足如下规则:

  • read 和load ,store和write 必须一对操作
  • 不允许线程丢弃assign操作,变量在工作内存中改变后必须把变化同步回主内存
  • 不允许一个线程无原因地没有发生过任何(assign操作)把数据从线程的工作内存同步回主存中
  • 新变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量(load或assign),对一个变量实时use和store之前必须先执行过assign和load操作
  • 一个变量同一时刻值允许一条线程lock操作,但lock操作可以重复多次,执行相同数量的unlock,变量才会解锁
  • lock操作会清空工作内存副本,执行引擎使用前,需要执行load或者assign操作初始化变量的值
  • 没有lock操作,就不允许unlock操作。不允许unlock另一个线程变量。
  • unlock操作前必须先把此变量同步回主内存中(执行store,write操作)

  虚拟机允许将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行。

Happens-Before原则

  • 程序顺序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。
  • 管理锁定规则:一个unlock操作先于后面对同一个锁的lock操作。
  • Volatile变量规则:对一个volatile变量的写操作必须在对该变量的读操作之前发生。
  • 线程启动规则:线程的Thread.start()方法必须在该线程所有其他操作之前发生。
  • 线程终止规则:线程中所有操作都先行发生于该线程的终止检测。可以通过Thread.join()方法结束、Thread.isAlive()的返回值判断线程是否终止。
  • 线程中断规则:对线程interrupt()方法的调用必须在被中断线程的代码检测到interrupt调用之前执行。
  • 对象终结规则:对象的初始化(构造函数的调用)必须在该对象的finalize()方法完成。
  • 传递性:如果A先行发生于B,B先行发生于C,那么A先行发生于C。

关键概念

  并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

  并行:在操作系统中是指,一组程序按独立异步的速度执行,不等于时间上的重叠(同一个时刻发生)。

  多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。

  同步就是协同步调,按预定的先后次序进行运行。

  线程安全:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这几个类都能表现出正确的行为,那么就成这个类是线程安全的。