硬件:处理器,高速缓存和主内存间的交互关系
image.png
线程,主内存,工作内存的关系
image.png
- 线程对变量所有操作(读取,赋值)等必须在工作内存中进行,而不能直接读写内存中的变量
- 不同的线程之间也无法访问对方的工作内存的变量
- volatile变量依然有工作内存拷贝,但是它每次操作都会读取最新的主内存数据,所以造成了像是直接在主内存操作
内存间交互操作
java 规定了八种操作完成吧主内存拷贝到工作内存,如何从工作内存同步回主内存之类的实现细节。
- lock:作用于主内存的变量,把一个变量标识位一个线程独占的状态
- unlock:作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后变量的才可以被其他线程锁定
- read:作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存,以便随后的load动作使用。
- load:作用于工作内存,他把从主内存得到的变量值放入工作内存的变量副本中。
- use:作用于工作内存的变量,它把工作内存一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign:作用于工作内存的变量,他把一个从执行引擎接收到的值赋值给工作内存变量。
- store:作用于工作内存的变量,它把工作内存中的变量的值传递到主内存,以便随后的write操作
- write:作用于主内存的编内,它把从工作内存中得到的变量值放入工作内存的变量中。
如果我们要把变量从主内存复制到工作内存,那就必须严格按照read和load顺序操作,要把变量从工作内存同步到主内存,则必须store-write。
但是上述操作没有必须连续执行可以插入其他的指令 - 不允许read和load 和store-write单独出现。
- 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把状态同步到主内存
- 不允许一个线程没有发生过assign变量的数据同步到主内存
-一个新的变量只能在主内存诞生,不允许在工作内存直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作 - lock多少次 必须unlock多少次
- lock会情况工作内存中此变量的值,在执行引擎使用该变量值,需要重新load或assign
- 不允许对未lock的进行unlock操作
- 对一个变量执行unlock之前,必须把此变量同步回主内,即store和write操作
volatile操作的规则
-
包装此变量对所有线程的可见性,即当线程修改了变量的值,新值对于其他线程是理解可知的,即每次使用volatile变量都需要重新去主内存获取
-
禁止指令重排序优化,即依赖该变量值的程序都不会重排序。通过内存屏障来达到禁止重排序
内存屏障可以被分为以下几种类型
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
当线程T操作volatile变量V和W的时候,需要满足如下的规则:
- 即每次使用变量都从主内存获取最新值
-每次值改变了后都会立刻同步回主内存
-假设操作A是线程T对于变量V实施的use或assign,P是与动作A相关的read或write,类似地动作B是线程T对W的use或assign,G是与动作B相关的read或write。那么如果A先于B,那么P一定优先于G,即禁止指令重排序。 - long和double允许把对64位数据的读写操作,变为两次32位操作,但是jvm的实现一般都是把他们变为原子操作
现行发生原则
- 程序次序规则,在一个线程内的,按照程序代码的属性,书写在前面的操作现行发生于书写在后面的操作。(重排序只是不影响线程执行结果)
- 管程锁定规则,unlock必须发生在lock之后
- volatile规则:对volatile变量的写操作现行发生于后面对这个变量的读操作
- 线程启动规则:start操作现行发生于线程的每一个动作
- 如果A操作现行发生于B操作,操作B现行于发生操作C,那就可以得出操作A现行发生于操作C的结论
线程实现的3种方式
- 内核线程:使用内核支持的线程,切换和调度都由内核完成,坏处就是每个操作都需要系统调用代价较高,且需要消耗一定的内核资源,所以数量有限
- 用户线程:线程可以不需要切换到内核状态,所以操作非常快且消耗低,可以支持大规模的线程数量,劣势就是没有系统内核的支持,所有线程操作需要用户程序自己处理,线程创建切换和调度都需要考虑
- 用户线程和内核线程:既可以大规模的使用线程,且线程的执行速度很快,亦可以通过与内裤联系,这样用户线程的调度可以使用操作系统来进行
java 线程调度
两种调度
-
协同式线程调度:线程执行的时间由线程本身来控制,线程执行完成通知系统切换到另外一个线程,最大好处是实现简单,坏处就是线程的执行时间不可控制,导致程序一直阻塞
-
抢占式调度:由系统分配执行时间,线程切换由操作系统来控制
线程的五种状态
- 新建,刚new出来
- 运行,包含running和ready也就是调用了start方法后
-无线等待:locksupport的park,object的wait,thread.join
-限期等待,上面无线等待设置日期的
- 阻塞:阻塞状态和等待状态,前者等待的一个排他锁,后者则是在等待被唤醒
-
结束:
image.png
网友评论