并发的三大特性

  • 原子性
  • 有序性(对应的是重排序)
    • 编译器重排序
    • cpu 指令重排序
    • cpu 内存重排序(这种重排序会导致内存可见性问题)
  • 可见性
    • cpu 层面是因为在每一个核心里面有各种各样的 buffer,虽然不同核心的 L1, L2 之间因为 cpu 的缓存一致性不会出现内存可见性的问题,但是 store buffer 和 L1 之间是异步的,这是导致可见性问题的根源。
    • java 层面是因为所有变量的操作都是在 工作内存中,执行完成后才会同步的 主内存 里面,这会导致可见性问题。
    • 可见性问题的解决方式是使用 内存屏障 ,对应到 java 中就是可以使用 volatile 关键字来实现。

内存屏障

使用 volatile 变量时会加入内存屏障,可以保证以下两点

  • 可见性
    • 写屏障(storefence)保证在该屏障之前的对共享变量的改动,都同步到主内存当中
    • 读屏障(loadfence)保证在该屏障之后的对共享变量的读取,加载的都是主内存中最新的数据
  • 有序性
    • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
    • 读屏障会确保在指令重排序时,不会将读屏障之后的代码排在读屏障之前

volatile 的三个作用

  1. 64 位写入的原子性
  2. 内存可见性
  3. 禁止重排序

疑问

  1. 64 位写入的原子性

两个线程一个设置 long 类型的值,一个读取 long 类型的值,存在几率会让读取的数值不对,解决的方式如下, 但是这里有一个问题,volatile 的作用是解决内衣可见性,而出现这个问题应该是原子性,volatile 不能解决原子性吧?
image.png
这里在后续也给出了答案,在jdk5 之后,对 volatile 进行了语义增强,在此之前,一个 64 位的 long/double 型变量的读写操作可以被拆分成两个 32 位的读/写 操作来执行,jdk5 之后,任务的读操作都必须具有原子性。

ThreadLocal 内容

https://www.yuque.com/docs/share/d8dd93b7-3d85-4e44-866b-1fda059db65d?# 《InheritedThreadLocal 支持继承性的原理》