Java并发编程(2)-并发原理

摘要

咱们这一讲主要讲解基于volatile实现并发:可见性跟有序性问题,讲解volatile的时候,须要讲解:cpu缓存模型 -> java内存模型 -> 并发编程3大特性:原子性、可见性、有序性 -> volatile的做用 -> volatile的底层原理 -> volatile实战。java

思惟导图

内容

cpu多级缓存模型

1.volatile引入

咱们先看下如下例子:编程

public class VolatileTest {
     static int flag = 0;
    public static void main(String[] args) {
        /**
         * 一、开启一个读线程,读取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();
        /**
         * 二、开启一个读写线程,修改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();
    }
}

结果输出为:
image.png缓存

经过以上咱们发现:
一、上面代码实例中:flag是静态成员变量,存在与方法区;此方法区里面的资源是线程共享的资源,线程共享资源在多线程数据读取跟修改时候,会出现线程安全问题。而localFlag是方法栈里面的变量是线程私有的数据。每次是把堆里面的数据读取出来赋值给栈里面的数据。
二、共享数据在多线程读写时候,会出现线程不一致性问题;读线程不可以准确读取到写线程修改的数值。
三、在实际的系统运行过程当中,可能会产生一个问题,就是说,Thread1修改变量的值,他修改了这个变量的值,结果呢,发现Thread2在他修改变量值以后,没那么快能感知到flag数值的变化。Thread1,已经将flag设置好了;可是Thread2,好比说在一段时间范围内,仍是读到了旧的flag,在一小段时间范围内,可能Thread2会感知不到Thread1对flag的值修改,他读到的可能仍是flag的这么一个旧的值。 安全

咱们将变量加上volatile修饰多线程

static volatile int flag = 0;

输出结果以下:
image.png
咱们发现加上volatile以后,每次读线程都可以准确获取到volatile修饰的数据。并发

并发编程中:你只要开了多个线程,必定会有一些这种问题,某个线程修改一个变量值,其余线程要马上感知到这个变量值的变化,可是若是你不用volatile,会有问题:有线程修改了一个变量的值,结果其余的线程感知不到这个变量值的修改ide

volatile:并发编程中,一个线程修改了某个共享变量值,其余线程会马上感知到这个变量值的变化。volatile只是保证数据可见性,有些人说他是一个轻量级锁,实际上是打错特错的。性能

2.cpu多节缓存模型

理想中的cpu模型:
image.png
假如咱们的cpu内存模型如上:第二个cpu进行数据的读写,第一个cpu进行数据读取,每次第二个cpu会先从主存读取数据,而后进行写操做,将不会存在数据不一致问题,由于主内存里面的数据都是最终的数据。每次cpu1都是读取的主内存里面数据。每次读取时候,只要主内存里面数据更改了,cpu1就可以读取到最新的值。 spa

现代计算机cpu内存模型因为:内存的读取速度跟不上cpu的处理速度。因此引出了;内存的读写速度没什么突破,cpu若是要频繁的读写主内存的话,会致使性能较差,计算性能就会低。因此引出了:cpu多级缓存模型: 线程

image.png
一、如今计算机为了平衡主内存跟cpu处理速度协调问题,在其之间增长了多级cpu缓存。
二、cpu能够直接操做本身对应的cpu缓存,不须要直接频繁的跟主内存通讯,这个是现代计算机技术的一个进步,这样能够保证cpu的计算的效率很是的高。
三、这样的cpu多级缓存模型将会致使主内存里面的实时数据在其余cpu线程里面读取不到,会有一个延迟。

cpu多级缓存致使数据不一致问题的分析。
image.png

3.总线加锁机制和MESI缓存一致性原理

早期数据不一致问题使用总线加锁机制保证。
总线加锁机制:
原理: 某个线程修改主内存里面共享数据的时候,会经过一个总线,将共享数据加锁,而后这个线程执行完相应操做以后才会释放锁。
问题: 效率低下:总线加锁机制使得多线程串行化执行,多线程下效率低下。

目前比较经常使用的是缓存一致性原理:MESI
MESI缓存一致性原理:
原理: 强制刷新主内存+cpu嗅探机制:某一个线程修改某一个数值时候,会将此数值强制刷新到主内存,而后使用cpu的嗅探机制发布一个修改数据的事件,而后其余线程嗅探到此数据被修改,而后使本地cpu缓存数据过时,而后把从主内存里面从新加载。
底层原理: lock前缀指令+内存屏障