并发三大特性

可见性

当一个线程修改了共享变量的值,其他线程能够看到修改的值。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的。
如何保证可见性:

  • 通过volatile关键字保证可见性。
  • 通过内存屏障保证可见性。
  • 通过synchronized关键字保证可见性。
  • 通过Lock保证可见性。
  • 通过final关键字保证可见性

有序性

即程序执行的顺序按照代码的先后顺序执行。JVM存在指令重排,所以存在有序性问题。
什么是指令重排序:在不影响程序结果/语义的情况下,JVM可以优化我们的指令顺序

  1. //在我们new一个Person类的时候,会进行3步
  2. //1:现在堆空间开辟一块内存
  3. //2:初始化person对象(调用构造函数)
  4. //3:把初始化好的person对象指向刚创建出来的堆空间地址
  5. //但是在2和3的步骤下,有可能会进行指令重排,
  6. //因为不管是先初始化后指向地址,还是先指向地址后初始化,都不影响结果,
  7. //所以这种情况下是有可能被指令重排的。
  8. Person person=new Person()

如何保证有序性:

  • 通过volatile关键字保证可见性。
  • 通过内存屏障保证可见性。
  • 通过synchronized关键字保证有序性。
  • 通过Lock保证有序性。

原子性

一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。不采取任何的原子性保障措施的自增操作并不是原子性的。
例如:i++就不是原子操作

  1. //i++不是原子操作,他的操作是分成三步的
  2. //1:先获取i的值
  3. //2:i自增+1
  4. //3:把自增的值再赋值给i
  5. //在多线程的情况下,所以i++不是线程安全的,因为第2,3步有可能被打断。
  6. i++

如何保证原子性:

  • 通过synchronized关键字保证原子性。
  • 通过Lock保证原子性。
  • 通过CAS保证原子性。

Java内存模型(JMM)

JMM定义

Java虚拟机规范中定义了Java内存模型(JavaMemoryModel,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。JMM描述的是一种抽象的概念,一组规则,通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式,JMM是围绕原子性、有序性、可见性展开的。
截屏2022-03-10 00.29.50.png

内存交互操作

主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

volatile

volatile的特性

  • 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
  • 有序性:对volatile修饰的变量的读写操作前后加上各种特定的内存屏障来禁止指令重排序来保障有序性。

volatile写-读的内存语义

  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。