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

摘要

我们这一讲主要讲解基于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();
    }
}

结果输出为:
Java并发编程(2)-并发原理_第1张图片

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

我们将变量加上volatile修饰

 static volatile int flag = 0;

输出结果如下:
Java并发编程(2)-并发原理_第2张图片
我们发现加上volatile之后,每次读线程都能够准确获取到volatile修饰的数据。

并发编程中:你只要开了多个线程,一定会有一些这种问题,某个线程修改一个变量值,其他线程要立刻感知到这个变量值的变化,但是如果你不用volatile,会有问题:有线程修改了一个变量的值,结果其他的线程感知不到这个变量值的修改

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

2.cpu多节缓存模型

理想中的cpu模型:
Java并发编程(2)-并发原理_第3张图片
假如我们的cpu内存模型如上:第二个cpu进行数据的读写,第一个cpu进行数据读取,每次第二个cpu会先从主存读取数据,然后进行写操作,将不会存在数据不一致问题,因为主内存里面的数据都是最终的数据。每次cpu1都是读取的主内存里面数据。每次读取时候,只要主内存里面数据更改了,cpu1就能够读取到最新的值。

现代计算机cpu内存模型由于:内存的读取速度跟不上cpu的处理速度。所以引出了;内存的读写速度没什么突破,cpu如果要频繁的读写主内存的话,会导致性能较差,计算性能就会低。所以引出了:cpu多级缓存模型:

Java并发编程(2)-并发原理_第4张图片
1、现在计算机为了平衡主内存跟cpu处理速度协调问题,在其之间增加了多级cpu缓存。
2、cpu可以直接操作自己对应的cpu缓存,不需要直接频繁的跟主内存通信,这个是现代计算机技术的一个进步,这样可以保证cpu的计算的效率非常的高。
3、这样的cpu多级缓存模型将会导致主内存里面的实时数据在其他cpu线程里面读取不到,会有一个延迟。

cpu多级缓存导致数据不一致问题的分析。
Java并发编程(2)-并发原理_第5张图片

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

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

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

你可能感兴趣的:(jvm)