面试题-java锁,高并发,多线程-1

并发编程的三要素是什么(线程的安全性问题体现在哪)?

原子性:一个或多个操作要么全部执行成功,要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到(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读重排。

你可能感兴趣的:(java)