java高并发系列 - 第4天:JMM相关的一些概念

JMM(java内存模型),因为并发程序要比串行程序复杂不少,其中一个重要缘由是并发程序中数据访问一致性安全性将会受到严重挑战。如何保证一个线程能够看到正确的数据呢?这个问题看起来很白痴。对于串行程序来讲,根本就是小菜一碟,若是你读取一个变量,这个变量的值是1,那么你读取到的必定是1,就是这么简单的问题在并行程序中竟然变得复杂起来。事实上,若是不加控制地任由线程胡乱并行,即便本来是1的数值,你也可能读到2。所以咱们须要在深刻了解并行机制的前提下,再定义一种规则,保证多个线程间能够有小弟,正确地协同工做。而JMM也就是为此而生的。java

JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来创建的。咱们须要先了解这些概念。安全

原子性

原子性是指操做是不可分的,要么所有一块儿执行,要么不执行。在java中,其表如今对于共享变量的某些操做,是不可分的,必须连续的完成。好比a++,对于共享变量a的操做,实际上会执行3个步骤:性能优化

1.读取变量a的值,假如a=1
2.a的值+1,为2
3.将2值赋值给变量a,此时a的值应该为2多线程

这三个操做中任意一个操做,a的值若是被其余线程篡改了,那么都会出现咱们不但愿出现的结果。因此必须保证这3个操做是原子性的,在操做a++的过程当中,其余线程不会改变a的值,若是在上面的过程当中出现其余线程修改了a的值,在知足原子性的原则下,上面的操做应该失败。并发

java中实现原子操做的方法大体有2种:锁机制无锁CAS机制,后面的章节中会有介绍。高并发

可见性

可见性是指一个线程对共享变量的修改,对于另外一个线程来讲是不是能够看到的。有些同窗会说修改同一个变量,那确定是能够看到的,难道线程眼盲了?性能

为何会出现这种问题呢?优化

看一下java线程内存模型:线程

  • 咱们定义的全部变量都储存在主内存
  • 每一个线程都有本身独立的工做内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
  • 线程对共享变量全部的操做都必须在本身的工做内存中进行,不能直接从主内存中读写(不能越级)
  • 不一样线程之间也没法直接访问其余线程的工做内存中的变量,线程间变量值的传递须要经过主内存来进行。(同级不能相互访问)

线程须要修改一个共享变量X,须要先把X从主内存复制一份到线程的工做内存,在本身的工做内存中修改完毕以后,再从工做内存中回写到主内存。
若是线程对变量的操做没有刷写回主内存的话,仅仅改变了本身的工做内存的变量的副本,那么对于其余线程来讲是不可见的。
而若是另外一个变量没有读取主内存中的新的值,而是使用旧的值的话,一样的也能够列为不可见。code

共享变量可见性的实现原理:

线程A对共享变量的修改要被线程B及时看到的话,须要进过如下步骤:

1.线程A在本身的工做内存中修改变量以后,须要将变量的值刷新到主内存中
2.线程B要把主内存中变量的值更新到工做内存中

关于线程可见性的控制,可使用volatilesynchronized来实现,后面章节会有详细介绍。

有序性

有序性指的是程序按照代码的前后顺序执行。

为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的前后顺序,好比程序。

int a = 1;  //1
int b = 20; //2
int c = a + b; //3

编译器优化后可能变成

int b = 20;  //1
int a = 1; //2
int c = a + b; //3

上面这个例子中,编译器调整了语句的顺序,可是不影响程序的最终结果。

在单例模式的实现上有一种双重检验锁定的方式,代码以下:

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

咱们先看instance = new Singleton();

未被编译器优化的操做:

  1. 指令1:分配一款内存M
  2. 指令2:在内存M上初始化Singleton对象
  3. 指令3:将M的地址赋值给instance变量

编译器优化后的操做指令:

  1. 指令1:分配一块内存S
  2. 指令2:将M的地址赋值给instance变量
  3. 指令3:在内存M上初始化Singleton对象

如今有2个线程,恰好执行的代码被编译器优化过,过程以下:

最终线程B获取的instance是没有初始化的,此时去使用instance可能会产生一些意想不到的错误。

如今比较好的作法就是采用静态内部内的方式实现:

public class SingletonDemo {
    private SingletonDemo() {
    }
    private static class SingletonDemoHandler{
        private static SingletonDemo instance = new SingletonDemo();
    }
    public static SingletonDemo getInstance() {
        return SingletonDemoHandler.instance;
    }
}

java高并发系列交流群

相关文章
相关标签/搜索