共计 1650 个字符,预计需要花费 5 分钟才能阅读完成。
并发编程的三要素是什么(线程的安全性问题体现在哪)?
原子性:一个或多个操作要么全副执行胜利,要么全副执行失败。
可见性:一个线程对共享变量的批改,另一个线程可能立即看到(synchronized,volatile)。
有序性:程序执行的程序依照代码的先后顺序执行。(有序性不代表禁止指令重排)。
什么是 JAVA 内存模型?
首先,JAVA 内存模型是指 JMM,而不是指内存构造,内存构造是在物理上的区域划分,而 JMM 则是抽象概念上的划分。
JMM(内存模型)次要包含两块:主内存 + 工作内存。
主内存:多个线程间通信的共享内存称之为主内存,即,数据是多个线程工共享的,在物理内存构造上通常对应“堆”中的线程共享数据。
工作内存:多个线程各自对应本人的本地内存,即,数据只属于该线程本人的,在物理内存构造上通常对应“本地办法栈”中的线程公有数据。
Java 内存模型规定了所有的变量都存储在主内存 (Main Memory) 中,每条线程还有本人的工作内存 (Working Memory),线程的工作内存中保留了被该线程应用到的变量的主内存正本拷贝,线程对变量的所有操作(读取、赋值等) 都必须在工作内存中进行,而不能间接读写主内存中的变量,不同的线程之间也无奈间接拜访对方工作内存中的变量,线程间变量值得传递均须要通过主内存来实现。
volatile 关键字的作用
Java 提供了 volatile 关键字来保障可见性和禁止指令重排(肯定有序)。volatile 提供 happens-before 的保障,确保一个线程的批改能对其余线程是可见的。当一个共享变量被 volatile 润饰时,它会保障批改的值会立刻被更新到主存,当有其余线程须要读取时,它会去内存中读取新值。
Volatile 是怎么保障可见性的?
对 volatile 润饰的变量,执行写操作的话,JVM 会发送一条 lock 前缀指令给 CPU,CPU 在计算完之后会立刻将这个值写回主内存,同时因为有 MESI 缓存一致性协定,所以各个 CPU 都会对总线进行嗅探,本人本地缓存中的数据是否被他人批改。
如果发现他人批改了某个缓存的数据,那么 CPU 就会将本人本地缓存的数据过期,而后这个 CPU 上执行的线程在读取那个变量的时候,就会从主内存从新加载最新的数据。
小结:lock 前缀指令 + MESI 缓存一致性协定。
Volatile 能保障强一致性吗?
不能。可见性能够认为是最弱的“一致性”(弱统一),只保障用户见到的数据是统一的,但不保障任意时刻,存储的数据都是统一的(强统一)。它只能保障线程过去读取数据时,能获取到以后的最新数据。
什么是 MESI 缓存一致性协定?
M(批改, Modified): 本地处理器曾经批改缓存行, 即是脏行, 它的内容与内存中的内容不一样. 并且此 cache 只有本地一个拷贝(专有)。
E(专有, Exclusive): 缓存行内容和内存中的一样, 而且其它处理器都没有这行数据。
S(共享, Shared): 缓存行内容和内存中的一样, 有可能其它处理器也存在此缓存行的拷贝。
I(有效, Invalid): 缓存行生效, 不能应用。
过程:
Core0 批改 v 后,发送一个信号,将 Core1 缓存的 v 标记为生效,并将批改值写回内存。
Core0 可能会屡次批改 v,每次批改都只发送一个信号(发信号时会锁住缓存间的总线),Core1 缓存的 v 放弃着生效标记。
Core1 应用 v 前,发现缓存中的 v 曾经生效了,得悉 v 曾经被批改了,于是从新从其余缓存或内存中加载 v。
Volatile 是怎么做到禁止指令重排的?
对于 volatile 批改变量的读写操作,都会退出内存屏障。
每个 volatile 写操作后面,加 StoreStore 屏障,禁止下面的一般写和他重排;每个 volatile 写操作前面,加 StoreLoad 屏障,禁止跟上面的 volatile 读 / 写重排。
每个 volatile 读操作前面,加 LoadLoad 屏障,禁止上面的一般读和 voaltile 读重排;每个 volatile 读操作前面,加 LoadStore 屏障,禁止上面的一般写和 volatile 读重排。