| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 复习(一)从Volatile到CAS -> 正文阅读 |
|
[Java知识库]复习(一)从Volatile到CAS |
Volatile随着学而不用后,又被我忘在脑后,重新学习一下。 目录 Volatie是什么?Volatile是Java虚拟机提供的轻量级的同步机制,它有两个功能:保证线程对共享变量的可见性,和禁止指令重排。 为什么说是轻量级? 这要从Java的内存模型说起。 Java的内存模型
Java的线程之间的通信由Java的内存模型(JMM)所控制,JMM决定了一个线程堆共享变量的写入何时对另一个线程可见。 从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有个本地内存,本地内存中存储了该线程以读/写共享变量的副本。 JMM就是一个抽象的概念,它并不真实存在,它描述的是一种规范。 如下图所示: 根据上图来看,如果线程A和线程B要进行通信,则要经历下面两个步骤:
从整体来看,这两个步骤实际上是线程A向线程B发送消息,而且这个通讯过程必须要经过主内存。JMM控制主内存与每个线程本地内存之间的交互,来为Java程序提供内存可见性保证。 JMM有三大特性:
而Volatile实现了其中两种:可见性和有序性,因此说它是轻量级的同步机制。 Volatile可见性验证通过前面对JMM的介绍,我们知道,各个线程对共享变量的操作都是各个线程各自拷贝到自己本地的工作内存进行操作再写回到主内存中的。 这就可能存在一个问题: 假设线程AAA修改了共享变量X的值但还未写回主内存时候,线程BBB又对主内存的共享变量X进行了操作,但此时A线程工作内存中共享变量X对线程B来说并不可见。 这种工作内存与主内存同步延迟的现象造成了可见性问题。 创建资源类定义i=0,addTo60的方法使i=60
线程操纵资源类线程aaa操纵资源类,使i变为60,另外一条线程则是main线程,i==0的时候进入了无限循环
当这个程序开始执行后,线程aaa在沉睡三秒以后对共享变量i进行了赋值到60,而mian线程因为不知道i已经变为60,还以为i==0在无限循环中,程序一直无法结束。 而把共享变量i用volatie修饰以后,程序就能执行下去了,main线程知道了共享变量i已经不再为0了。
Volatile不保证原子性volatile能够保证可见性,却不能保证原子性,也就是在某个线程正在执行每个具体业务时,中间不可以被加塞或者被分割,需要整体完整,也就是数据的完整性。 volatile不保证原子性代码演示资源类
线程操作资源类,创建20个线程执行对变量i++的操作,每个线程执行1000次。理想情况,最终i的结果会等于20000。
实际上程序执行的结果却总是丢失: 为什么最终结果会不等于20000? 要知道各线程是把共享变量拿到自己工作内存中进行修改,再写会主内存,当 i = 0,ABC线程都拿到i=0的数据,执行i++操作,假设A线程在写完i=1的时候,因为上下文切换,被挂起,值还未写回主内存。B线程将写好的值放回主内存,通知其他线程值已经改变,但线程执行速度是很快的。在还未反应过来的前提下,A把i=1写回了主内存,覆盖了B线程写的值i=1,主线程里的值本应该相加到2 ,却被1覆盖了。 出现了丢失写值的情况,这就为什么最终结果不等于20000的原因。 如何保证原子性?synchronize同步锁能够使以上代码变成原子性操作,最终实现了线程安全。虽然synchronize能够保证线程安全,但是在某些情况下,这并不是一个最优选择。
synchronize关键字会让没有得到锁资源的线程进入BLOCKED的状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统优化模式和内核模式的转换,代价比较高。 尽管Java 1.6 为synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。 面对这种情况,我们就可以使用java中的原子操作类。 所谓原子操作类,指的是 java.util.concurrent.atomic包下,一系列以Amotic开头的包装类。如果AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子操作。 使用原子类保证原子性getAndIncrement()方法就是原子类中的i++操作,它使最终的结果变为正确的20000,这样的代码性能会比synchronized更好。 ?为什么Atomic能够保证原子性且性能较好?因为Atomic操作类的底层用到了CAS机制。 CASCAS是什么?CAS的全称为Compare and swap,它是一条CPU并发原语。 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。 CAS并发原语体现在Java语言中sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。只是一种完全依赖于硬件的功能。通过它实现了原子操作。 再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU原子指令,不会造成所谓的数据不一致的问题。 从代码体验CAS说白了,CAS就是更新之前,比较下变量是否更期望值一样,如果是,则更新成功。
CAS源码解析查看Atomic原子类的源码,以getAndIncrement()方法举例,可以看到三个参数,当前对象,内存偏移量,1。 内存偏移量来自与Unsafe.class,Unsafe类存在于sun.misc包中,其内部方法可以像C操作指针一样直接操纵内存,因为CAS操作的执行依赖于Unsafe类的方法。 继续查看getAndIncrement()调用的getAndAddInt()方法的源码,可以看到:先获取当前对象var1和var2内存偏移量,赋值给var5。 如果compareAndSwapInt()方法中,当前对象var1和内存偏移量var2刚好等于var5,var5则加上var4(要增加的数字),并返回true,比较并替换成功。结果取反,就跳出循环。 如果对比失败,则返回false,取反false,就一直在while循环中比较,直到成功为止。 以上代码,我们拿两个线程AB为例,根据JMM内存模型,线程A和线程B各自从主内存拿到共享变量value的值5(假设这个值为5)到自己的工作空间修改,执行getAndAddInt()操作。 A线程通过getIntVolatile(var1,var2)拿到value值5 B线程通过getIntVolatile(var1,var2)拿到value值5,此时刚好B线程没有挂起并执行了compareAndSwapInt()方法,比较主内存的值也为5,对比成功,把值更新为6 A线程这时醒过来,通过compareAndSwapInt()方法对比,发现工作内存里的数字5和主内存中的6不一致,说明该值已被其他线程更新过了。A线程本次修改失败,只能重新读取再来一次。 A线程重新获取value值,因为变成value被volatile修饰,所以其他线程对它的修改,A线程总是能看到,线程A继续执行compareAndSwapInt()进行比较替换,直到成功(这是一个自旋的过程) CAS简单小总结CAS(CompareAndSwap) 比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较知道主内存和工作内存的值一致为止。 CAS应用 CAS有三个操作数,内存值V,旧的预期值A,要修改的更新值B。 只有当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS的缺点
循环时间长 开销大可以看到底层源码中有个do{}while{}的循环,如果遇到极端情况,CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大开销。 只能保证一个共享变量的原子操作从源码可见只能保证当前对象 ABA问题在多线程环境中,当某个共享变量被一个线程连续重复读取两次,那么只要第一次和第二次读取的值一样,那么这个线程就会认为这个变量在两次读取时间间隔内没有任何变化。 假设 线程1 期望值为A 想要把值更新为B 线程2 期望值为A 想要把值更新为B 线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望值A比较,发现相等更新为B。这个时候出现了线程3,期望值为B,希望更新为A,发现主内存的值就是B,更新为A成功。这是线程2从阻塞中恢复,获得CPU时间片,这时线程2取值与期望值A比较,发现相等把值更新为B。 虽然线程2也完成了操作,但线程2并不知道已经经过了A-B-A的变化过程。 如果要解决ABA问题,做到严谨的CAS机制,我们在compare阶段不仅仅是要比较期望值A和地址V的实际值,还要比较变量的版本号是否一致。 在Java中,AtomicStampedReference类实现了用版本号做比较的CAS机制。
? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 8:05:25- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |