深入理解java虚拟机读书笔记,第十二章:Java内存模型与线程

12.1概述

衡量一个服务端的好坏,每秒事物处理数(Transactions Per second,TPS)是最重要的指标之一


12.2硬件的效率与一致性

基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂性,引入了新的问题:缓存一致性

为了使处理器内部运算单元尽量充分利用,处理器会对输入代码乱序执行优化

Java 虚拟机的即时编译器中也有类似的指令重排序优化


12.3Java内存模型

Java虚拟机规范定义了一种内存模型(JAVA Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现Java语言在各个平台都能达到一致的内存访问效果

12.3.1主内存和工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节,此处的变量和Java编程中所说的有所区别,它包括实例变量、静态字段、和构成数组对象的元素,但是不包括局部变量和方法参数,后者是线程私有的,不会被共享,自然就不会存在竞争问题

Java内存模型规定了所有的变量都存储在主内存(Main memory)中,每条线程还有自己的工作内存(Working Memory),线程的工作内存保存了被该线程使用的变量的主内存副本拷贝

线程对变量的操作(读取、赋值)必须都在工作内存中进行,而不能直接读写主内存中的变量

不同的线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递都通过主内存传递

12.3.2内存间交互操作

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步到主内存,Java内存模型定义了8种操作来完成;每一种操作都是原子的、不可再分的(long和double有例外)


lock(锁定):作用于主内存变量,把一个变量状态标识一条线程独占的状态

unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

read(读取):作用于主内存变量,把一个变量的值从主内存传输到工作内存中,以便随后的load动作使用

load(载入):作用于工作内存变量,把read操作从主内存得到的变量值放入工作内存的变量副本中

use(使用):作用于工作内存变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令的时候都会执行这个操作

assign(赋值):作用于工作内存,把执行引擎返回的结果值赋给工作内存的变量

store(存储):作用于工作内存的变量,把工作内存中的一个值传递到主内存中去,以便随后的write操作

write(写):作用于主内存变量,把store操作从工作内存中等到的变量值放到主内存的变量中去


执行8中操作时候必须满足的规则

不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了,在工作内存不接受,或者从工作内存发起回写,主内存不接受的情况

不允许一个线程丢弃掉它最近的assign操作,即变量在工作内存改变以后必须把该变化同步到主内存

不允许一个线程无原因的(没有发生过任何assign)把数据从工作内存同步到主内存

一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过assign和load操作

一个变量在同一时刻只能有一个线程对其进行lock操作,但是lock操作可以被同一线程重复执行多次,多次lock执行后,只有执行相同次数的unlock操作,变量才会被解锁

如果对变量执行lock操作,将会清空工作内存此变量的值,在执行引擎使用这个变量钱前,需要重新load或者assign初始化变量的值

如果一个变量事先没有被lock,就不允许执行unlock操作,也不允许unlock一个被其他线程lock的变量

对一个变量unlock之前,必须把此变量同步到主内存


先行发生原则:用来确定一个访问在并发环境下是否安全

12.3.3对于volatile型变量的特殊规则

Java虚拟机提供的轻量级同步机制


作用:

1>保证此变量对所有线程的可见性

不存在一致性问题

Java语言里的运算并非原子性,导致volatile变量的运算在并发下是安全的

在不符合以下两条规则的场景中,仍要通过加锁(使用synchronize或java.util.concurrent中的原子类)来保证原子性

运算结果并不依赖于变量的当前值,或者能够确保只有单一的线程修改变量的值

变量不需要与其他的状态变量共同参与不变约束

2>禁止指令重排序优化


特殊规则

每次使用Volatitle变量前必须先从主内存刷新最新的值

工作内存中修改volatile变量后必须同步到主内存中

volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序和代码顺序一致


12.3.4对于long和double型变量的特殊规则

允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机不保证64位数据类型的load、store、read和write这4个操作的原子性,这就是long和load的非原子性协定

目前商业虚拟机不会出现读取到“半个变量"的情况

12.3.5原子性、可见性、有序性

原子性

基本数据类型的访问读写是具备原子性的(例外是long和double的非原子性协定)

synchronized块之间的操作具备原子性


可见性

当一个线程修改变量的值,其他线程能够立刻得知这个修改

synchronized、volatile、final都能保证可见性


有序性

如果在本线程内观察,所有的操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的

synchronized、volatile保证有序性

12.3.6先行发生原则

程序次序规则:按程序代码顺序

管程锁定规则:unlock优先于后面对同一个锁的lock操作

volatile变量规则:写优先于后面对这个变量的读操作

线程启动规则:start()方法先行于每个动作

线程终止规则:线程中所用操作先行于对此线程的终止检测

线程中断规则:对线程的interrupt()方法的调用优先发生于被中断线程的代码检测到的中断事件的发生

对象终结规则:初始化先行发生于finalize()

传递性

12.4Java与线程

12.4.1线程的实现

3种方式:

1>使用内核线程实现

直接由操作系统内核支持的线程,由内核完成线程切换,内核通过操纵调度器进行线程调度,并且负责将线程的任务映射到各个处理器上

程序一般不会直接使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process ,LWP),轻量级进程就是我们通常意义上讲的线程

轻量级进程和内核线程之间1:1的关系称为一对一线程模型


轻量级进程的局限性

基于内核实现,各种线程操作需要进行系统调用,而系统调用的代价相对较高,需要在用户态(User Model)和内核态(Kernel Model)中来回切换

每个轻量级进程都需要一个内核线程支持,轻量级进程要消耗一定的内核资源,因此一个系统支持轻量级进程的数量是有限的


2>使用用户线程实现

狭义的用户线程指的是完全建立在用户空间的线程库,系统内核不能感知线程存在

进程与线程之间的1:N的关系称为一对多的线程模型

优势在于不需要内核支援,劣势也在于没有内核支援

实现比较复杂,使用用户线程的程序越来越少


3>使用用户线程加轻量级进程实现

用户线程完全建立在用户空间,用户线程的创建、切换、析构的操作比较廉价,并且支持大规模的用户线程并发

操作系统提供支持的轻量级进程则作为用户线程和内核线程的桥梁

使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险


4>Java线程的实现

JDK1.2之前,是基于称为“绿色线程”的用户线程实现的

JDK1.2中,替换为基于操作系统的原生线程模型

操作系统支持怎么样的线程模型,很大程度上决定了Java虚拟机的线程怎么实现


12.4.2Java线程调度

线程调度是指系统为线程分配处理器使用权的过程


两种主要调度方式:

1>协同式线程调度

执行时间由线程本身决定,执行完后,主动通知系统切换到另一个线程

好处是实现简单;切换操作对线程自己可知,所以没有什么同步问题;

坏处是执行时间不可控;可能会出现程序一直阻塞的情况


2>抢占式线程调度

线程由系统分配执行时间,切换不由线程本身决定

执行时间系统可控,不会有一个线程导致整个进程阻塞的情况


Java使用的线程调度方式是抢占式线程调度

建议给系统分配时间:线程优先级

12.4.3状态转换

5种状态:

新建(New):创建后尚未启动

运行(Runable):包括操作系统线程状态中的Running和Ready,也就是正在执行和等待CPU为它分配执行时间的

无限期等待(Waiting):不会被CPU分配时间,要等待被其他线程显式地唤醒

没有设置Timeout参数的Object.wait()

没有设置Timeout参数的Thread.join()

LockSupport.park()

限期等待(Timed Waiting):不会被CPU分配时间,一定时间后会自动唤醒

Thread.sleep()

设置Timeout参数的Object.wait()

没有设置Timeout参数的Thread.join()

LockSupport.paNanos()

LockSupport.parkUntil

阻塞(Blocked):线程被阻塞乐乐

“阻塞状态”和“等待状态”的区别

“阻塞状态”在等待一个排它锁

“等待状态”实在等待一段时间或者唤醒动作的发生

结束(Terminated):已终止的线程状态,线程已经执行结束







参考文献:

[1] 深入理解Java虚拟机 第二版 --周志明


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容