并发的三大特性
- 原子性
- 有序性(对应的是重排序)
- 编译器重排序
- cpu 指令重排序
- cpu 内存重排序(这种重排序会导致内存可见性问题)
- 可见性
- cpu 层面是因为在每一个核心里面有各种各样的 buffer,虽然不同核心的 L1, L2 之间因为 cpu 的缓存一致性不会出现内存可见性的问题,但是 store buffer 和 L1 之间是异步的,这是导致可见性问题的根源。
- java 层面是因为所有变量的操作都是在 工作内存中,执行完成后才会同步的 主内存 里面,这会导致可见性问题。
- 可见性问题的解决方式是使用 内存屏障 ,对应到 java 中就是可以使用 volatile 关键字来实现。
内存屏障
使用 volatile 变量时会加入内存屏障,可以保证以下两点
- 可见性
- 写屏障(storefence)保证在该屏障之前的对共享变量的改动,都同步到主内存当中
- 读屏障(loadfence)保证在该屏障之后的对共享变量的读取,加载的都是主内存中最新的数据
- 有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保在指令重排序时,不会将读屏障之后的代码排在读屏障之前
volatile 的三个作用
- 64 位写入的原子性
- 内存可见性
- 禁止重排序
疑问
- 64 位写入的原子性
两个线程一个设置 long 类型的值,一个读取 long 类型的值,存在几率会让读取的数值不对,解决的方式如下, 但是这里有一个问题,volatile 的作用是解决内衣可见性,而出现这个问题应该是原子性,volatile 不能解决原子性吧?
这里在后续也给出了答案,在jdk5 之后,对 volatile 进行了语义增强,在此之前,一个 64 位的 long/double 型变量的读写操作可以被拆分成两个 32 位的读/写 操作来执行,jdk5 之后,任务的读操作都必须具有原子性。
ThreadLocal 内容
https://www.yuque.com/docs/share/d8dd93b7-3d85-4e44-866b-1fda059db65d?# 《InheritedThreadLocal 支持继承性的原理》