jvm八之内存模型与线程

主内存与工作内存

在这里插入图片描述

 1. 所有的变量都存储在主内存
 2. 线程的工作内存中保存了被该线程使用的变量的主内存副本
 3. 不同的线程之间也无法直接访问对方工作内存中的变
量,线程间变量值的传递均需要通过主内存来完成
 4. 线程对变量的所有操作(读取、赋值等)都必须在工作内
存中进行,而不能直接读写主内存中的数据

在这里插入图片描述
与处理器,高速缓存,主内存之间的对应关系可以这样理解,主内存对应物理硬件的内存,工作内存对应对应寄存器和高速缓存.缓存一致性协议和java提供的save和load指令都是为了保证主内存数据一致性.

内存间交互操作

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

ues <= load <= read
assign => store => write

volatile

申明变量共享,编译器不会把该变量与其他内存操作重排序。不会被缓存寄存器或者其他,因此读取会返回最新值
         执行时,不会添加锁,所以不会线程阻塞
         如果在验证正确性需要对可见性进行复杂判断,不要使用。
         作用包括,确保自身可见性,他们所引用对象的可见性,标识重要程序生命周期事件的发生(初始化or关闭)
         只能确保可见性,无法确保原子性

指令重排

从硬件架构上讲,指令重排序是指处理器采用了允许将多条指令
不按程序规定的顺序分开发送给各个相应的电路单元进行处理。

volatile修饰的变量,赋值后多执行了一个“lock addl$0x0,(%esp)”操作,这个操作的作用相当于一个内存屏障
(Memory Barrier或Memory Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置)
lock addl$0x0,(%esp)指令把修改同步到内存时,意味着所有之
前的操作都已经执行完成,这样便形成了“指令重排序无法越过内存屏障”的效果。

可见性

如果没有重排序,在方法中只要为了保证结果一致,a会乱序,乱序造成a的值会混乱,而Volatile禁止重排序后,执行那个流程a是多少就是多少
a=5
T1 获取a=5
T2 获取a=5
T3 获取a=5
这时T1修改a=6.主内存a=6.而T2,T3还是5,是因为他们保存的a的副本没有过期
那么让T2,T3保存的a的副本过期的话,他们在获取a的值就会从主内存获取,而这就是Volatile能保证可见性原因

除了volatile之外,Java还有两个关键字能实现可见性,它们是synchronized和final。同步块的可见
性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操
作)”这条规则获得的。而final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完
成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通
过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看见final字段的值。

原子性

a=5
T1 获取a=5
T2 获取a=5
T3 获取a=5
T1修改a=6的时候,有一个轻微lock机制

a=5
lock
a=6
unlock
在lock之前,获取a的值依旧是5,在锁过程中,获取a的线程会阻塞,在锁完成后,阻塞的线程和之后的线程获取的是a=6

64位数据类型.譬如long需要使用Volatile,是因为long存在2个寄存器里面Volatile让2寄存器合在一起处理

场景

        1:对变量的写入操作不依赖变量当前值,或者你能确保只有单个线程更新变量值
           当只有单个值修改时,值修改后,其他内存过期.由于可见性,另外一个线程直接主内存获取最新的值
        2:该变量不会与其他状态变量一起纳入不变性条件中
            A线程修改时,B线程修改的时候,重复修改操作,B在A修改前值赋予给了寄存器,且进行了操作.这个时候A执行成功,B因为获取值了不会再次获取
        3:访问变量时不需要加锁

Java与线程

实现线程主要有三种方式:使用内核线程实现(1:1实现),使用用户线程实现(1:N实现),
使用用户线程加轻量级进程混合实现(N:M实现)。

内核线程实现

内核线程(Kernel-Level Thread,KLT)就是直接由
操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调
度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视
为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就称为多线程内核
(Multi-Threads Kernel)。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light
Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个
内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间11
的关系称为一对一的线程模型.

由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使其中某一个轻量级进程
在系统调用中被阻塞了,也不会影响整个进程继续工作。轻量级进程也具有它的局限性:首先,由于
是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调
用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。其次,每个
轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈
空间),因此一个系统支持轻量级进程的数量是有限的。

在这里插入图片描述

用户线程实现

广义上来讲,一个线程只要不是内核线程,都可以认
为是用户线程(User Thread,UT)的一种,因此从这个定义上看,轻量级进程也属于用户线程,但轻
量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,因此效率会受到限制,并不
具备通常意义上的用户线程的优点。

而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存
在及如何实现的。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如
果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也能够支持
规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之
间1:N的关系称为一对多的线程模型.
用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都
需要由用户程序自己去处理。线程的创建、销毁、切换和调度都是用户必须考虑的问题,而且由于操
作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”“多处理器系统中如何将线程映射到其他处
理器上”这类问题解决起来将会异常困难,甚至有些是不可能实现的。因为使用用户线程实现的程序通
常都比较复杂,除了有明确的需求外(譬如以前在不支持多线程的操作系统中的多线程程序、需要
支持大规模线程数量的应用),一般的应用程序都不倾向使用用户线程。Java、Ruby等语言都曾经使
用过用户线程,最终又都放弃了使用它。但是近年来许多新的、以高并发为卖点的编程语言又普遍支
持了用户线程,譬如Golang、Erlang等,使得用户线程的使用率有所回升。

在这里插入图片描述

用户线程加轻量级进程混合实现

将内核线程与用户线程一
起使用的实现方式,被称为N:M实现。在这种混合实现下,既存在用户线程,也存在轻量级进程。
用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以
支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,
这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来
完成,这大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量
比是不定的,是N:M的关系

在这里插入图片描述

Java线程调度

线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式
(Cooperative Threads-Scheduling)线程调度和抢占式(Preemptive Threads-Scheduling)线程调度。
如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行
完了之后,要主动通知系统切换到另外一个线程上去.
抢占式调度的多线程系统,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。但可以设置优先级,优先级越高的线程越容易被系统选择执行。但具体依赖操作系统实现.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值