摘要
咱们这一讲次要解说基于 volatile 实现并发:可见性跟有序性问题,解说 volatile 的时候,须要解说:cpu 缓存模型 -> java 内存模型 -> 并发编程 3 大个性:原子性、可见性、有序性 -> volatile 的作用 -> volatile 的底层原理 -> volatile 实战。
思维导图
内容
cpu 多级缓存模型
1.volatile 引入
咱们先看下以下例子:
public class VolatileTest {
static int flag = 0;
public static void main(String[] args) {
/**
* 1、开启一个读线程,读取 flag 的值
*/
new Thread(){
@Override
public void run() {
int localFlag = flag;
while (true){if(localFlag != flag){System.out.println("读取到的标识位值:"+flag);
localFlag = flag;
}
try {TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {e.printStackTrace();
}
}
}
}.start();
/**
* 2、开启一个读写线程,批改 flag 值
*/
new Thread(){
@Override
public void run() {
int localFlag = flag;
while (true){System.out.println("标识位被批改了:"+ ++localFlag);
flag = localFlag;
try {TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {e.printStackTrace();
}
}
}
}.start();}
}
后果输入为:
通过以上咱们发现:
1、下面代码实例中:flag 是动态成员变量,存在与办法区;此办法区外面的资源是线程共享的资源,线程共享资源在多线程数据读取跟批改时候,会呈现线程平安问题。而 localFlag 是办法栈外面的变量是线程公有的数据。每次是把堆外面的数据读取进去赋值给栈外面的数据。
2、共享数据在多线程读写时候,会呈现线程不一致性问题;读线程不可能精确读取到写线程批改的数值。
3、在理论的零碎运行过程中,可能会产生一个问题,就是说,Thread1 批改变量的值,他批改了这个变量的值,后果呢,发现 Thread2 在他批改变量值之后,没那么快能感知到 flag 数值的变动。Thread1,曾经将 flag 设置好了;然而 Thread2,比如说在一段时间范畴内,还是读到了旧的 flag,在一小段时间范畴内,可能 Thread2 会感知不到 Thread1 对 flag 的值批改,他读到的可能还是 flag 的这么一个旧的值。
咱们将变量加上 volatile 润饰
static volatile int flag = 0;
输入后果如下:
咱们发现加上 volatile 之后, 每次读线程都可能精确获取到 volatile 润饰的数据。
并发编程中:你只有开了多个线程,肯定会有一些这种问题,某个线程批改一个变量值,其余线程要立即感知到这个变量值的变动,然而如果你不必 volatile,会有问题:有线程批改了一个变量的值,后果其余的线程感知不到这个变量值的批改
volatile:并发编程中,一个线程批改了某个共享变量值,其余线程会立即感知到这个变量值的变动。volatile 只是保证数据可见性,有些人说他是一个轻量级锁,其实是打错特错的。
2.cpu 多节缓存模型
现实中的 cpu 模型:
如果咱们的 cpu 内存模型如上:第二个 cpu 进行数据的读写,第一个 cpu 进行数据读取,每次第二个 cpu 会先从主存读取数据,而后进行写操作,将不会存在数据不统一问题,因为主内存外面的数据都是最终的数据。每次 cpu1 都是读取的主内存外面数据。每次读取时候,只有主内存外面数据更改了,cpu1 就可能读取到最新的值。
古代计算机 cpu 内存模型因为: 内存的读取速度跟不上 cpu 的处理速度。所以引出了;内存的读写速度没什么冲破,cpu 如果要频繁的读写主内存的话,会导致性能较差,计算性能就会低。所以引出了:cpu 多级缓存模型:
1、当初计算机为了均衡主内存跟 cpu 处理速度协调问题,在其之间减少了多级 cpu 缓存。
2、cpu 能够间接操作本人对应的 cpu 缓存,不须要间接频繁的跟主内存通信,这个是古代计算机技术的一个提高,这样能够保障 cpu 的计算的效率十分的高。
3、这样的 cpu 多级缓存模型将会导致主内存外面的实时数据在其余 cpu 线程外面读取不到,会有一个提早。
cpu 多级缓存导致数据不统一问题的剖析。
3. 总线加锁机制和 MESI 缓存一致性原理
晚期数据不统一问题应用总线加锁机制保障。
总线加锁机制:
原理: 某个线程批改主内存外面共享数据的时候,会通过一个总线,将共享数据加锁,而后这个线程执行完相应操作之后才会开释锁。
问题: 效率低下: 总线加锁机制使得多线程串行化执行,多线程下效率低下。
目前比拟罕用的是缓存一致性原理:MESI
MESI 缓存一致性原理:
原理: 强制刷新主内存 +cpu 嗅探机制: 某一个线程批改某一个数值时候, 会将此数值强制刷新到主内存,而后应用 cpu 的嗅探机制公布一个批改数据的事件,而后其余线程嗅探到此数据被批改,而后使本地 cpu 缓存数据过期,而后把从主内存外面从新加载。
底层原理: lock 前缀指令 + 内存屏障