JVM---内存模型是如何处理高并发问题

并发应用场景:

TPS:
衡量一个服务器性能的高低好坏,TPS(Transactions Per Second)是最重要的指标,它代表着一秒内服务端平均能响应的请求总数,而TPS与程序并发能力有关。

物理计算机中的并发问题:

处理器与内存交互,读取运算数据,存储运算结果。如何提高让并发执行若干个运算任务。可以先将数据缓冲到Cache中,当运算结束后再从缓存同步回内存。

如何确保缓存一致性(Cache Coherence)
在这里插入图片描述
处理器与高速缓存和主内存间交互关系如上图,这样的话,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution),处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,因此如果存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。

那么同样的对于Java虚拟机的即时编译器中也有类似的指令重排序优化。

Java内存模型

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存类似前面讲的处理器的高速缓存。线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写在主内存中的变量。不同的线程之间的工作内存互不可见。
在这里插入图片描述
如果局部变量是一个reference类型,它引用的对象在Java堆中可被各个线程共享,但是reference本身在Java栈中的局部变量表中,是线程私有的。

内存间交互操作
交互协议:一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存之类的实现细节,Java内存模型定义了8种操作可以来完成。
当然每种操作都是原子的,不可再分(对于double和long类型的变量来说,load/store/read/write操作在某些平台上允许有例外。)

lock(锁定
unlock(解锁)
read(读取)
load(载入)
use(使用)
assign(赋值)
store(存储)
write(写入)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Volatile变量可见性和禁止指令重排序。

在这里插入图片描述
由于运算不是原子操作,A操作数据变量在栈顶的时候,可能已经被B已经又操作了,这样导致的结果就是假设图中A.B.C三个线程同时增加一个被volatile修饰的变量的时候,会得到的这个变量与理论值不一致的情况。

所以如何控制变量原子性呢?
加锁:(synchronized或者java.util.concurrent中的原子类)来保证原子性

如果不加锁通过volatile变量来控制并发需要满足如下场景:
1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
2、变量不需要与其他的状态变量共同参与不变约束。

线程内表现为串行的语义》指令重排序会干扰程序的并发执行
在这里插入图片描述
由于代码编译会进行优化,JIT即时编译优化技术,指令重排序,将一些代码优化后执行,如果伪代码中没有用volatile修饰,那么initialized=true可能先被执行。

那么加了volatile修饰,变量会在原有执行赋值的过程中,多执行一个内存屏障,指重排序时候不能把后面的指令重排序到内存屏障之前的位子。当然是针对多个CPU执行引擎来说的,只有一个不需要。

由于double和long不是原子性变量,所以如果多个线程共享一个并未申明volatile的long或者double类型的变量,并且同时对他们进行读取和修改操作。那么某些线程可能会读取到一个既非原值,也不是其他线程修改值的代表了“半个变量”的数值。不过Java虚拟机不会出现这种情况他们会被认为是原子操作。因此对于long和double的变量专门申明为volatile是没有必要的。

原子性、可见性与有序性

原子性:
基本数据类型的访问读写是具备原子性的,原子性变量操作包括read/load/assign/use/store/write。
更大范围的原子性保证的话,需要lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式使用这两个操作,这两个字节码指令反映到java代码中就是同步快—synchronized关键字。

可见性:
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。
普通变量和volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次用之前从主内存刷新。因此,volatile保证了多线程操作时候的变量的可见性。
其他关键字可以实现可见性的是synchronized和final。
在这里插入图片描述
前提是被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那么在其他线程就能看见final字段的值。

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

先行发生原则:

happens-before。
作用是判断数据是否存在竞争,线程是否安全的主要依据,依靠这个原则,我们可以通过几条规则解决并发环境下两个操作之间是否可能存在冲突的所有问题。

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

在这里插入图片描述
可以判断下如果A->B中间再执行下C,那么j有几种结果。
答案是两种可能1也可能是2.

所以Java语言也有顺序原则,防止随意重排序。
在这里插入图片描述
Java线程的实现3种方法:
使用内核线程实现(KLT),使用用户线程实现(UT),使用用户线程加轻量级进程(LWP)混合实现。
1:1
在这里插入图片描述
1:N
在这里插入图片描述
现在一般只有DOS的多线程程序与少数特殊需求使用。
N:M
在这里插入图片描述
Java线程的调度
协同式线程调度,抢占式线程调度。
Lua–》协同例程坏处:线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,则程序就会一直阻塞。
好处是实现简单。

Java抢占式线程调度
在这里插入图片描述

5种线程状态

New / Runnable/ Waiting / Time Waiting / Blocked Terminated.
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小诚信驿站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值