当前位置:网站首页>JVM学习(六)-内存模型和线程

JVM学习(六)-内存模型和线程

2020-11-09 12:00:44 IT迷途小书童

高效并发

TPS:每秒事务处理数。

1. JAVA的内存模型

JAVA内存组织为主内存和工作内存两部分。

(1) 主内存

所有线程所共享的。主要包括本地方法区和堆。

(2) 工作内存

每个线程都有一个工作内存,不是共享的。工作内存包含两部分。

l 该线程私有的栈

主内存部分变量拷贝的寄存器(程序计时器PC和工作的告诉缓冲区)

(3) 内存间的交互操作

为了方便理解内存间的操作,需要理解是那几部分之间的内存进行操作。主要是以下三个部分:主内存【lockunlockreadstorewrite】、工作内存和执行引擎。

主内存:【lockwriteunlock

主内存->工作内存 :【read

工作内存:【load=read的结果赋值给工作内存的变量副本。

工作内存->执行引擎:【use

执行引擎->工作内存:【Assign

工作内存->主内存 :【store

主内存:【write=

单独执行引擎:

l Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。

l Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。

l Load(加载):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。

l Use(使用):作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。

l Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。

l Store(存储):作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中。

l Write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中

l Unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,之后可被其它线程锁定。

 

两个原则

在将变量从主内存读取到工作内存中,必须顺序执行readload

要将变量从工作内存同步回主内存中,必须顺序执行storewrite

(4) volatile型变量的特殊规则

Volatile提供了一种轻量级的同步手段。只能保证多线程的可见性,不能保证多线程的有序性。

任何被volatile修饰的变量,都不拷贝副本到工作内存。任何修改都及时写入主内存。因此,对于volatile修饰的变量的修改,所有线程马上能看到。但是不能保证修改有序

Volatile 怎样做到可见性的。对于变量的修改,立即同步到主内存中。线程使用之前,从主内存同步过来。

Synchronize同步块的可见性有“对一个变量unlock之前,必须把变量同步到主内存中”这条规则获得的。

满足条件下的原子性

如果使用volatile来实现线程安全。需要满足以下条件:

  1. 变量的写操作不依赖变量当前的值。
  2. 该变量没有包含在具有其他变量的不变式中

当对longdouble类型的变量用关键字volatile修饰时,就能获得简单操作(赋值和(return)的原子性。

l 禁止指令重排序优化

通过插入内存屏障保证一致性。

(5) 原子性、可见性、有序性
  1. 原子性

定义:简单的说,原子性的操作就是不可被中断的操作,这个操作要么还未开始,要么全部完成。

Java内存模型保证原子性的操作readloaduseassignstorewrite

Lockunlock 可以保证更大范围的原子性。

Synchronize 的原子性是用更高层次的字节码指令monitorenter monitorexit 来隐式操作的。

  1. 可见性

定义:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改

l Volatile :volatile 的特殊规则保证了新值能立马同步到主内存中。每次使用变量之前都保证从主内存刷新。因此,保证了多线程之间的可见性。

l Synchronize :同步块的可见性,由“对一个变量执行unlock之前,必须同步到主内存中”这条规则获得的。

l Finalfinal修饰的关键字一旦在构造器方法初始化赋值完成之后,变量数值不被修改。各个线程得到的变量都是初始化之后的赋值。不会发生变化。

  1. 有序性

如果在本线程内观察,所有操作都是有序的。如果在一个线程观察另外一个线程,所有操作都是无序的。前半句指“线程内变现为串行的语义”,后半句指“指令重排”和“工作内存和主内存同步延迟”现象。

l Java通过volatilesynchronize两个关键字来保证线程之间的操作是有序的。Volatile本身禁止指令重排。Synchronize通过“一个变量同一时刻只允许一个线程对其进行lock操作”,从而决定了同一个锁的两个同步块只能串行进入。

(6) 先行发生原则

l 定义

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

l 作用

也就是 happens-before 原则。这个原则是判断数据是否存在竞争线程是否安全的主要依据。先行发生是 Java 内存模型中定义的两项操作之间的偏序关系。

2. JAVA线程

CPU和内核的理解:

CPU是中央处理器的缩写。而内核是CPU的核心。CPU包含多个核。

(1) 线程的实现
使用内核线程的实现

内核线程就是直接由操作系统内核支持的线程,这种线程由内核来实现在每个线程上切换,内核通过操作操纵调度器多线程进行调度,并负责把线程的任务映射到各个处理器,每个内核线程可以视为内核的分身,支持多线程的内核叫做多线程内核

由操作系统内核支持的线程,这种线程由内核完成切换。程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口---LWP(轻量级进程)--轻量级进程是内核线程的一种高级接口由于内核的支持,每个轻量级的进程,都有一个独立的内核级线程的支持。很容易理解,一个轻量级进程阻塞,不会影响其它线程的工作【因为有独立的内核支持】。

 

局限性

由于内核实现,所以线程的各种操作需要的代价较高【各种操作都需要使用系统调用,同时需要在用户态(user mode)和内核态(kernel mode)之前来回切换】。内核个数是固定的。所以,内核线程数也是确定的。

 

 

 

使用用户线程的实现

用户线程值完全建立在用户空间的线程库上,这种线程不需要切换内核态。效率高且消耗低。也支持规模更大的线程数量。部分高性能数据库中的多线程就是使用用户线程来实现的。

个人理解(不确定正确):用户空间对java程序而言,就是JAVA虚拟机JVM。而

进程和用户线程之间的关系是1N的关系。

 

 

 

(2) 线程调度

线程调度指为线程分配处理器使用权限的过程。分两种,分别是协同式线程调度和抢占式线程调度。

协同式线程调度

线程执行时间由线程本身来控制。线程自己操作完成之后,主动通知系统切换到另外的线程。好处:实现简单,且切换对线程自己可知。没有线程同步问题。缺点:如果一个线程阻塞,整个进程都会阻塞【不能主动让出CPU】。

抢占式线程调度

线程执行时间有系统分配执行时间。线程切换不由线程本身决定。线程执行时间系统可控,不会有一个线程导致整个进程阻塞。

JAVA线程调度就是抢占式线程调度

(3) 用户模式和内核模式

目的:为了不让程序任意存取资源,大部分的CPU架构都支持Kernel modeUser mode两种执行模式。

和零拷贝技术息息相关。

内核模式(Kernel Mode)

在内核模式下,代码具有对任何硬件的所有控制权限。可以执行所有的CPU指令,可以访问任意的地址内存。内核模式是操作系统的最底层,最可信的函数服务。在内核模式下,任意异常都是灾难性的。可能导致整台机器宕机。

用户模式(User Mode)

用户模式下,代码没有对硬件的控制权。也不能直接访问地址的内存。程序是通过系统接口来访问硬件和内存的。在这种保护模式下,即使程序发生了奔溃,也是可以恢复的。

用户态与内核态的切换

所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作.

这时需要一个这样的机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行的指令

这种机制叫系统调用, CPU中的实现称之为陷阱指令(Trap Instruction)

他们的工作流程如下:

1.用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.

2.用户态程序执行陷阱指令

3.CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问

4.这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务

5.系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果

 

个人描述

用户程序中,运行在用户模式user mode下,需要对硬件进行操作。首先,程序不能直接对硬件进行操作,所以调用系统函数接口。然后,对调用进行检测、权限验证等。没问题之后,再通知CPU切换到kernel模式。依照应用程序传递过来的参数执行特权指令。当程序执行完成后,系统调用会通知CPU切换到用户模式,并回到应用程序中。

为什么要有用户态和内核态

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态 和 内核态

版权声明
本文为[IT迷途小书童]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/maopneo/p/13947821.html