juc 并发包的基础 cas 与 volatile

CAS :

即一种对内存中的数据进行操作的指令,而且该操作是原子的操作其过程如下:首先CPU将内存中的将要被修改的数据与预期的值进行比较,如果这两个值相等,CPU则会将内存中数值替换为新值,否则不做操作。使用非阻塞算法,定义一个线程的失败或者挂起,是不会影响其它线程的失败或者挂起的。该操作是直接修改内存中的值,所以可以认为该操作是原子操作。但 CAS 存在 ABA 问题,假设存在 T1,T2 两个线程,T1 线程在内存中对参数 V 进行操作,先把参数 V 的值从 A 变为 B,T2 这是对参数 V 进行 CAS 操作是不成功的,因为此时参数 V 的值为 B,然后线程 T1 又把参数 V 从 B 修改为 A ,这时 T2 又可以对参数 V 进行 CAS 操作,此时又是成功的,但此时的成功不意味着没有问题,因为参数 V 在 T2 对其进行 CAS 操作时其值并不一直都是 A。

volatile:

volatile实现了多线程的有序性和可见性,但是并不具备原子性,

有序性:因为 cpu 执行的速度是非常高效的,为了防止一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。处理器在不改变执行结果的情况下可以改变指令的执行顺序。指令重排序对单线程没有什么影响,他不会影响程序的运行结果,但是会影响多线程的正确性。编译器重排序,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;处理器重排序,如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;但是对于被。对于 volatile 禁止指令重排序,是对被 volatile 修饰的变量汇编成的代码添加内存屏障(实质上是一组指令)进行处理器对其进行重排序,即是按照指令的排序顺序执行。

原子性: volatile 修饰的变量并不具体原子性特征。因为在内存底层对变量的修改是分三步操作完成的,例如 i++;要修改 i 的值,第一步线程会先到主存上把 i 的值读取到本地,第二步对 i 进行加 1 操作,第三步把 i 的值提交到主存上,如果是单线程环境下这是没问题的,但如果是多线程并发场景下,比如有 T1 和 T2 两个线程要对 i 的值进行修改,T1 线程需要对 i 进行加 1 操作,而 T2 线程需要对 i 进行加 2 操作,T1 , T2 把主存上 i 的值都读取到本地, T1 对 i 进行修改后在 T1 高速缓存中 i 的值为 i+1 , T2 对 i 进行修改后在 T2 高速缓存中 i 的值为 i+2 ,这时 T1 线程先把 i 的值提交到主存中,主存中 i 的值为 i+1 ,然后 T2 再把修改后 i 的值提交到主存中,这时主存中 i 的值就会变成 i+2,对于 T1 高速缓存中 i 的值就会变成一个脏数据,并不安全的。

可见性:对于 volatile 修饰的变量,会加入内存读屏障和内存写屏障,写屏障的作用是,某个线程对被 volatile 修饰的变量做修改时,会让这个变量的修改后的值即刻同步到主存上;读屏障的作用是当一个线程对被 volatile 修饰的变量进行修改时,会通过线程间的通信让其它线程高速缓存中对应的变量的值失效,强制让其线程重新去主存上面读取该变量最新的值,即当一个线程对被 volatile 修饰的变量做修改的时候,其它线程都能获取到最新的值,也就是一个线程对 volatile 修饰的变量做修改,其它线程是可见的。

总结:如果一个变量被 volatile 修饰时,如果再对这个变量的修改操作是 CAS 操作,那么这个变量就具备了有序性,可见性和原子性,那么对于这个变量的修改在多线程并发场景下就是安全的。 juc 并发包的基础就是volatile 和 CAS ,而 atomic 类就是对 volatile 和 CAS 联合使用最基本的操作。例如:一个线程对于 AtomicInteger 类的实例变量的修改操作,对其它线程都是可见的,而且是安全的。

    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_28140175/article/details/79899195
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞