java内存模型-spark

java内存模型-spark_第1张图片

java内存模型-spark_第2张图片

java内存模型-spark_第3张图片

java内存模型-spark_第4张图片

单独的Thread类,启动线程(同一段代码),相当于都是单独的
对象互不干扰,因为是几个线程就是几个thread对象。而Runnable接口来实现线程间的资源共享(同一段代码,类似卖票),
传入的是唯一的一个runnable子类,,票数是共享滴,只有那一个类,只不过数据不加synchronized会有混乱.(票数是成员变量,跟Runnable子类走的,不在主内存中)

原子性,即是不可分割性

不同代码之间的线程,共享外部全局变量要valatily

java内存模型(不同于jvm内存模型)

其实就看变量是不是共享的,线程内的成员变量(共享,会有混乱)和主线程的成员变量(copy,接下来的讲的就是这类情况)不一样
cpu cpu寄存器 --- cpu缓存  --  主内存(RAM)   根据摩尔定理CPU运算速度太快了,主内存跟不上,所以现在加上cpu缓存

java线程内存模型跟cpu缓存模型类似,是基于cpu缓存模型来建立的.
线程A<--->工作内存(共享变量副本)  <<--jmn控制-->>  主内存(共享变量-就是线程类之外的变量,这样才叫主内存)

一旦共享变量加了valatily,这个变量就是对所有线程都可见共享.没有volatile就都是自己的副本,互不影响

线程中总线活动
read(读取):从主内存读取数据。
load(下载):从主内存读取到工作内存
use(使用):从工作内存读取数据计算
assign(赋值):将计算好的值赋值给工作内存
store(存储):将工作内存数据写入到主内存
wirte(写入):将store过去的变量赋值给主内存中的变量
lock(锁定):将主内存变量加锁,标识为线程独占状态
unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量

以上八大原子性操作,多线程中的不保证顺序执行,既是可能a执行到write,而b才执行read

早期:
总线加锁(性能太低)
cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他cpu没法去读取或写这个数据,直到这个cpu使用完数据
释放锁之后其他cpu才能读取该数据。(类似读锁,还是并行只是操作同一个变量的时候,才会等待,执行其他代码的时候不会。在read的时候加)
后期优化:
MESI缓存一致性协议:
多个cpu从主内存读取同 一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据马上同步回主内存,
其他cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。(类似写锁,有修改变量其他的对象监听总线,
加锁,等待主内存修改完,释放,其他cpu再读取。在write时候加锁,锁的代码少,性能高)  volatile就是这样的

volatile底层非java实现
volatile缓存可见性实现原理:底层实现主要是通过汇编lock前缀指令,他会锁定这块内存区域的缓存并回写到主内存,此操作被称为
“缓存锁定”,MESI缓存一致性协议机制会阻止同时修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存值通过总线回写到
内存会导致其他处理器相应的缓存失效。

并发编程三大特性:可见性、原子性、有序性

 

volatile保证可见性与有序性,但是不保证原子性,保证原子性(既是单个线程整个逻辑过程)需要借助synchronized这样的锁机制。

有序性:代码执行是否按照写的一步步,自下而上。但是java虚拟机,不做任何限制的话,会有指令重排的可能,即是不一定第一行
会比第二行先执行,如果2行没有关系的话。
内存屏障:cpu不会给你指令重排(变量加了volatile,就会有内存屏障,保障你的代码是按照预想的顺序执行的)
int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4      

这里1,2无关系,不保证1先执行,3,4有关系则会保证后面执行.
//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
 //线程2:
while(!inited ){
   sleep()
}
doSomethingwithconfig(context);
线程1可能先执行语句2,导致线程2报错(context没有初始化)
inited加上volatile关键字,这样在对inited修改前,一定会保证之前的代码全部执行完了,就不会出现重排导致的问题了。


synchronized    有序性、原子性、可见性
jdk为何要设计锁:
多线程编程中,有可能出现多个线程同时访问同一个共享、可变资源的情况;这种资源可能是:对象、变量、文件等。
共享:资源可以由多个线程同时访问
可变:资源可以在其生命周期内被修改的
由于线程执行的过程是不可控的,所有需要采用同步机制来协同对对象可变状态的访问

原子性:最小的操作,必须作为一个整体搞完
jvm有8种原子内存操作,synchronized底层对应jvm八大原子操作中的lock和unlock这两个操作.

加锁:1、同步实例方法,锁是当前实例对象 2、同步类方法、锁是当前类对象  3、同步代码块、锁是括号里面的对象。
如果加在this对象上,需要bean的作用域是单例的(唯一的,才有用)。如果是原生的对象,哪怕加了,jvm也会自己去弄掉这个锁。
方法加static上,加锁加在当前实力对象的class对象上。
原理:
jvm内置锁通过synchronized使用,通过内部对象Monitor(监视器锁)实现,基于进入和退出Monitor对象实现方法与代码块同步,监视器锁
的实现依赖底层操作系统的Mutex lock(互斥锁)实现,他是一个重量级锁性
能较低(需要系统内核,去申请,伴随cpu类型的转变,用户态到内核态的转变(权限转变))。

new 一个对象的时候,jvm自然会给它配置一个Monitor对象,天生的。
对象内存结构:对象头、实例数据、对齐填充位。
对象头:mark World、对象的HashCode、Epoch、ThreadId、age(gc状态,默认15次复制算法,为什么会15,因为这个age大小是4位,最大能表示0~2的4次方-1,所以是15次)、偏向状态0或1、锁状态标志01、MetaData元数据指针、数组长度(数组对象才有)
hashcode就是对象在内存的地址
64位jvm中是12个字节   8位1个字节

假如是linux中的虚拟机,底层是调用linux中的Pthread mutex lock库去阻塞(c++)。

拿到锁对象执行完之后,释放会去唤醒(notify),阻塞队列中其他等待的线程。

偏向锁适用于:一个线程反复的进入同一个同步块。jvm设计者,觉得同步代码块,大部分都是一个线程一值在拿这个锁对象,执行。
只有换了,线程,才会锁升级。这样根据具体情况,具体执行,省略不必要的操作,才提升性能。

轻量级锁适用于:线程交替执行场景。
重量级锁适用于(性能低):高并发。(关闭锁的升级过程,省略升级过程的资源浪费,因为高并发,不像其他的,会有多种情况,而且线程不多)
无锁状态 -->> 偏向锁-->>轻量锁--->>重量锁

锁的升级状态,是可以手动关闭的,是不可逆的,只能升级锁级别,不能降底锁级别。

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

而对于普通变量,就直接是副本,无法影响主存,传递的是值,而不是地址值,就需要volatle来可见。

 

你可能感兴趣的:(web初级)