1,volatile用法:参考博客
上一篇主要介绍了基于AQS的独占锁和读写锁,其中state同步状态都是用volatile关键字修饰的。
作用:保持数据的可见性!
- 什么是不可见性的?
①了解java内存模型,JMM
线程通过将主存中共享变量副本保存在线程私有的本地内存,然后进行写操作。当一个线程修改了自己本地的共享数据副本,
还未提交到主存中时,如果同一时刻另一个线程访问了主存共享变量,这个变量还是修改之前的,那么线程1的修改操作对线程2不可见。
②volatile怎么保持可见性:---volatile字面意思--易变的
java规定:<1>被volatile修饰的变量,如果线程对其执行写操作,JMM会立即将共享变量值从线程本地内存刷新回主存。
<2>如果线程对被volatile修饰的变量执行读操作,会把立即该线程对应的本地内存置为无效,从主内存中读取共享变量的值。
以此来保证共享变量的可见性。
- volatile的内存语义
volatile关键字限制编译器指令重排序
①什么是指令重排序:
编译器,(处理器,内存系统),再单线程情况下,对不产生数据依赖的语句进行重排序。
原则:保证执行结果不变(单线程),进最大可能通过重排序提高性能。
②指令重排类型:
- 编译器优化:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 指令并行重排:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),
处理器可以改变语句对应的机器指令的执行顺序。
- 内存系统重排:由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,
导致内存与缓存的数据同步存在时间差。
③指令重排保证单线程下(串行)语义不变,但在多线程下就会有很多问题。
volatile限制指令重排:
①当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序,这个规则确保volatile写之前的操作不会被编译器重排到volatile写之后。
②当第一个操作时volatile读时,不管第二个操作是什么,都不能重排序,确保volatile读之后的操作不会排序到读之前
③当第一个操作是volatile写,第二个操作是volatile读时,禁止重排序,
实现原理是插入内存屏障,(读写屏障,写读屏障等等不探讨了。)
- volatile保证其修饰的变量的单个写或者,读是原子性的。不能像锁一样保证lock,与unlock之间的原子性,volatile对++i,i++
这种,先读后写或者先写后读,或者其他更复杂运算不能保证原子性。
2,并发编程3大特性:原子性,可见性,有序性。
- 可见性在上面分析volatile里说明了,保证一个线程对字段的修改才能确保对另一个线程可见
- 原子性:哪些指令必须是不可分割的。在Java内存模型中,这些规则需声明仅适用于-—实例变量和静态变量,也包括数组元素,
但不包括方法中的局部变量-—的内存单元的简单读写操作。
- 有序性:在什么情况下,某个线程的操作结果对其它线程来看是无序的。最主要的乱序执行问题主要表现在读写操作和赋值语句的相互执行顺序上。
3,synchronized关键字与volatile关键字:
①volatile关键字是线程同步的轻量级实现,性能比synchronized好,但volatile只能修饰变量,synchronized可以修饰方法和代码块
②volatile保证数据可见性,不能保证原子性。synchronized都能保证。
③volatile主要解决变量在多个线程之间的可见性。synchronized主要解决多个线程之间访问资源的同步性。