文章目录
- volatile的应用
- volatile的定义与实现原理
- 专业术语:
- volatile是如何来保证可见性的呢?
- volatile的原理:
- volatile的两条实现原则:(物理上如何实施)
- volatile的内存语义
- volatile的特性
- 例:下面这个,为什么能保障读的安全?
- volatile写-读的内存语义
- 为什么volatile有写后读还是不是线程安全的?
——————————————————————————
volatile的应用
volatile可以修饰变量
并发编程中有两种锁:
- synchronized
- volatile
volatile:
- volatile是轻量级的 synchronized
- volatile锁住的时候可以写,是只保证读是正确的
volatile在多处理器开发中保证了共享变量的“可见性” :
- 可见性:当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。
- 因此能保证读到的值是正确的
- 可见性一定是在多线程的情况下,一个线程对变量进行了修改,其他线程能立刻知道
- 每个线程对变量进行操作的时候都会拷贝一个副本数据
线程不安全是因为t1,t2,t3操作变量s时都会拷贝一份s副本,彼此不知道,t1给s+1,t2给s+2,t1先更新数据,t2后更新数据,这时cpu的s=2,而不是正确的3,这样线程不安全,可见性是指t1对变量s进行操作之后,t2,t3等其他变量拷贝的s的数据就会失效,需要再读一次s的数据,这样读到的数据就是新修改的数据。
- volatile修饰符使用恰当的话,比synchronized的使用和执行成本更低,因为volatile功能更少,它不会增加线程上下文的切换和调度,速度更快。
- cpu分配时间片的时候,volatile不会浪费掉
volatile的定义与实现原理
- volatile的用法就是修饰变量,声明一个变量然后用它修饰,多线程调用这个变量,其他不用改Java编程语言允许线程访问共享变量
- 共享变量指多线程指向同一个地址(一般是引用类型)
专业术语:
- 内存屏障:是一个约定,访问变量的时候先访问屏障看状态,例:如果是0就访问变量,如果是1就不访问变量
- 缓存行:(缓冲行)
高速缓存里面也是分成一份一份的,分成若干缓存行 - 原子操作: 指一系列操作要么都成功,要么都失败,要是一系列操作有5个步骤,前四个成功,最后一个失败,那么前四个都会还原
- 缓存行填充:让自己霸占缓存行,总线传数据的时候就不容易阻塞
- 缓存的概念:(什么叫做缓存)把数据存到一个离使用者更近的区域,让使用者访问更快
- 缓存有一个特点,就是存储容量比较小,价格也更贵,所以存储到缓存的数据都是访问频率更高的
- 缓存中的数据都有滞后性,需要隔一段时间更新一次
- 缓存命中:这里和操作系统的缓存不一样
cpu从内存读数据也很慢,所以cpu内部有高速缓存,cpu读取数据的时候先在高速缓存查看数据,如果有数据就叫做缓存命中。 - 写命中:cpu如果在高速缓存找到了目标的变量,对变量进行了写操作,就叫做写命中。
- 写缺失:一个有效的缓存行被写入到不存在的内存区域。缓存中写操作后的变量要刷新到内存的,写缺失是因为在多线程里,t1和t2对同一个变量进行操作,t1进行写操作后,从高速缓存刷新到内存之前,t2把变量删除了,导致写操作后的变量无法存入内存,就是写缺失。
volatile是如何来保证可见性的呢?
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
- 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。
- 如果对声明了volatile的 变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据 写回到系统内存。
- 但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操 作就会有问题。
- 所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
volatile的原理:
volatile的两条实现原则:(物理上如何实施)
cpu三个核,理论上三个线程可以同时运行,不会存在三个线程同时在内存刷新同一个数据的情况,因为操作内存一定是需要排队的,一个内存在一个时刻只能有一个线程操作
1)Lock前缀指令会引起处理器缓存回写到内存。
2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
- 总线是指令级别的排队,加入t1线程有20个指令,t2线程有10个指令,排队情况可能是t1的指令1—t2的指令1——t2的指令2…
- 锁总线是一个线程的全部指令执行完毕后总线才会释放,否则其他线程只能等待
- 锁总线代价很大
即,volatile在往内存写的时候有两种方案:锁总线、锁缓存
volatile的内存语义
前面讲的是volatile如何去使用,这里讲的是volatile里面是怎么实现的(理论依据)
前面的volatile操作是为了实现这些理论
volatile的特性
- volatile只能保障读是正确的,并不能保障多线程下的安全。
- volatile底层虽然也用了lock锁,所以volatile也是一种锁,但是不保证线程安全,即便是加了synchronized重锁,也不一定是线程安全的,保障了写后读才能保障多线程下的安全。
- volatile还能防止指令重排序,volatile修饰的变量,使用的时候,它的前后凡是涉及到写后读的是不能重排序的
要想保证并发安全,有且只能有写后读操作
例:下面这个,为什么能保障读的安全?
- 因为加锁之后,释放锁之后其他线程才能操作这个数据,写的时候不能读,读的时候不能写,不存在正在写的时候读(不会正在写的时候读),因此读的一定是正确的;
例:set get get get set get get set set get get get—》任何一个读操作都能看到最后一次写操作
上面这个,
volatile在这种情况下只能保障读是正确的,但这并不是volatile的全部功能
volatile写-读的内存语义
为什么volatile有写后读还是不是线程安全的?
因为—>要想保证并发安全,有且只能有写后读操作