zoukankan      html  css  js  c++  java
  • JVM学习(六)-内存模型和线程

    高效并发

    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划分出两个权限等级 -- 用户态 和 内核态

  • 相关阅读:
    git忽略已提交过的文件方法
    去除git版本控制
    写博客的初衷
    Substring with Concatenation of All Words
    Course Schedule
    Reverse Words in a String--not finished yet
    Repeated DNA Sequences
    Maximum Product of Word
    Odd Even Linked List
    Reorder List
  • 原文地址:https://www.cnblogs.com/maopneo/p/13947821.html
Copyright © 2011-2022 走看看