深入理解java虚拟机知识点—java内存模型

为什么要并发?

  1. 一个很重要的原因是计算机的运算速度与它的存储和通信子系统的差距太大,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。而让计算机同时处理几项任务则是最容易想到、也被证明是非常有效的“压榨”处理器运算能力的手段。
  2. 除了充分利用计算机处理器的能力外,一个服务端同时对多个客户端提供服务则是另一个更具体的并发应用场景。衡量一个服务性能的高低好坏,每秒事务处理数(Transactions Per Second,TPS) 是最重要的指标之一,他代表着一秒内服务端平均能响应的请求总数,而TPS值与程序的并发能力又有着非常密切的关系。

硬件的效率与一致性

绝大多数的运算任务都不可能只靠处理器“计算”就能完成,处理器至少要与内存交互,如读取运算数据、存储运算结果等,这个I\O操作是很难消除的(无法仅靠寄存器来完成所有的运算任务)

由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算结束后再从缓存同步回内存中,这样处理器就无须等待缓慢的内存读写了。

缓存一致性(Cache Coherence):为了解决缓存一致性问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI、MOSI、Synapse等。

乱序执行优化:为了使的处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。

java内存模型

主内存与工作内存

java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,工作内存中保存了该线程使用的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

从更低层次上说,主内存就直接对应于物理硬件的内存,为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

内存间交互操作

关于主内存与工作内存之间具体的交互协议,java内存模型中定义了以下8种操作来完成。虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的。

  • lock(锁定):作用于主内存,把一个变量标识为一条线程独占的状态
  • unlock(解锁)
  • read(读取):把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):把read操作从主内存中得到的变量值放入工作内存的变量副本中
  • use(使用):把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
  • assign(赋值):把从执行引擎接收到的值赋值给工作内存的变量
  • store(存储):把工作内存中一个变量的值传送到主内存中
  • write(写入):把store操作从工作内存中得到的变量的值放入主内存的变量中

注意:java内存模型要求顺序执行的操作:read --> load ;store --> write。但是只要求顺序执行,而没有保证是连续执行。

volatile型变量的特殊规则

当一个变量被定义为volatile之后,他将具备两种特性:

保证此变量对所有线程的可见性

这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。

volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性。

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
禁止指令重排序优化

普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。

内存屏障:https://www.jianshu.com/p/64240319ed60

对于long和double型变量的特殊规则

非原子协定

java内存模型要求lock、unlock、read、load、assign、use、store、write这8个操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义了一条相对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这4个操作的原子性。

先行发生原则

先行发生是java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。

java内存模型下一些“天然的”先行发生关系
  • 程序次序规则(Program Order Rule):在一个线程内,程序按照代码控制流程顺序
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作
  • 线程启动规则(Thread Start Rule)
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 线程终止规则(Thread Termination Rule)
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生
  • 对象终结规则(Finalizer Rule):一个对象的初始化(构造函数执行结束)先行发生于他的finalize()方法的开始
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论

java与线程

线程的实现
  • 使用内核线程实现
  • 使用用户线程实现
  • 使用用户线程加轻量级进程混合实现
java线程的实现

对于SunJDK来说,他的Windows版与Linux版都是使用一对一的线程模型实现的,一条java线程就映射到一条轻量级进程之中。

java线程调度

线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)抢占式线程调度(Preemptive Threads-Scheduling)

协同式线程调度

如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上

优点:实现简单,而且由于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知的,所以没有什么线程同步问题

缺点:线程执行时间不可控制

抢占式线程调度

如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定

发布了8 篇原创文章 · 获赞 1 · 访问量 262

猜你喜欢

转载自blog.csdn.net/qq_40635011/article/details/105429062
今日推荐