| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 多线程的三大特性 -> 正文阅读 |
|
[Java知识库]多线程的三大特性 |
目录 上下文切换概念:当因为某些原因造成cpu不再执行当前线程,转去执行另一个线程,就会发生上下文切换。当发生上下文切换时,操作系统会保存当前线程的状态,并且恢复另一个线程的状态。保存当前线程状态时就会涉及到JVM的一个内存结构:程序计数器(Program Counter Register),它是线程私有的,用于记录当前线程要执行的下一条命令的地址。 发生上下文切换的时机:
当发生上下文切换的时候,被恢复的线程读取的共享数据是从堆中读取的,而不是从本地变量副本读取 并行和并发的区别并行:指在同一时刻,多条指令在多个cpu上同时执行,不论时微观还是宏观上看,都是同时执行 ?并发:指多条指令在一个cpu上交替执行,同一时刻只能有一条指令在执行。但是由于交替的时间间隔很短,看不出来,所有宏观上来看是同时执行的,而微观上来看是分开交替执行的 ?并发三大特性(一) 可见性不可见性概念:当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会某些不可预期的问题 不可见原理: ?如图所示,共享数变量储在堆内存当中,而堆内存是唯一的,每个线程又有自己的线程栈,当线程要读取共享变量的时候,会先从堆内存中读取一份到线程栈中的变量副本中,之后读取该变量的时候会优先读取变量副本。而线程A在修改变量副本之后,会将堆中的共享变量的值进行更替,此时A的变量副本与堆内存中的共享变量都是最新的值,但是,线程B仍然读取的是之前的变量副本,两个线程读到的值就有所不同,这个的条件也相对苛刻,因为如果时间间隔较长不使用副本变量,就会对副本变量进行回收,然后下次读取还是会读取堆中的值。 可见性概念:当一个线程修改了共享变量的值,其他线程能够看到修改的值。 保证可见性的方式:
?并发三大特性(二) 原子性原子性问题 例如i++等操作执行过程是这样的,首先要先拿到内存中的数据,然后再保存到变量副本中,这时候在修改变量副本的值,然后再把变量副本的值替换掉内存中的值,在这个运算过程中走了三个步骤,在多线程环境中,可能修改好变量副本了,准备要往内存中复制这个过程中,就被其他线程修改了值,这时候再去复制,就导致结果最终不一致 解决方法:
?并发三大特性(三) 有序性有序性问题: JVM?会在不影响正确性的前提下,可以调整语句的执行顺序(指令重排),思考下面问题 图中左侧是 3 行 Java 代码,右侧是这 3 行代码可能被转化成的指令。可以看出 a = 100 对应的是 Load a、Set to 100、Store a,意味着从主存中读取 a 的值,然后把值设置为 100,并存储回去,同理, b = 5 对应的是下面三行 ?Load b、Set to 5、Store b,最后的 a = a + 10,对应的是 Load a、Set to 110、Store a。如果你仔细观察,会发现这里有两次“Load a”和两次“Store a”,说明存在一定的重排序的优化空间。 重排序后, a 的两次操作被放到一起,指令执行情况变为 Load a、Set to 100、Set to 110、 Store a。下面和 b 相关的指令不变,仍对应 Load b、 Set to 5、Store b。 可以看出,重排序后 a 的相关指令发生了变化,节省了一次 Load a 和一次 Store a。重排序通过减少执行指令,从而提高整体的运行速度,这就是重排序带来的优化和好处 但是在多线程情况下,就会发生一些不可预期的事情! ?解决方法:
通过 volatile 关键字保证有序性。
通过 内存屏障保证有序性。
通过 synchronized关键字保证有序性。
通过 Lock保证有序性。
volatile介绍valotile的作用: 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。 有序性:对volatile修饰的变量的读写操作前后加上各种特定的内存屏障来禁止指令重排序来保障有序性。
volatile写-读的内存语义
1. 在每个volatile写操作的前面插入一个StoreStore屏障
2. 在每个volatile写操作的后面插入一个StoreLoad屏障
3. 在每个volatile读操作的前面插入一个LoadLoad屏障
4. 在每个volatile读操作的后面插入一个LoadStore屏障
volatile禁止重排序场景:
valotile的实现方式: volatile在jvm层面上的实现是通过一个storeload内存屏障 在linux的x86系统中可以看到volatile的实现它实际上是调用汇编语言的lock前缀指令实现可见性的,它会使副本变量失效,强制性的要求下一次访问要从堆中获取最新的值。 ?lock前缀指令的作用:
1. LOCK前缀指令具有类似于内存屏障的功能,禁止该指令与前面和后面的读写指令重排
序。
2. LOCK前缀指令会
等待它之前所有的指令完成、并且所有缓冲的写操作写回内存
(也就是将store buffer中的内容写入内存)之后才开始执行,并且根据缓存一致性协议,刷新store buffer的操作会导致其他cache中的副本失效
jvm层面内存屏障
在JSR规范中定义了4种内存屏障:
LoadLoad屏障:
(指令Load1; LoadLoad; Load2),在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
LoadStore屏障:
(指令Load1; LoadStore; Store2),在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:
(指令Store1; StoreStore; Store2),在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
StoreLoad屏障:
(指令Store1; StoreLoad; Load2),
在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 12:42:29- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |