城市直播房产教育博客汽车
快传网
汽车报价
买车新车
博客专栏
专题精品
教育留学
高考读书
房产家居
彩票视频
直播黑猫
投资微博
城市上海
政务旅游

volatile关键字在并发中有哪些作用?

10月18日 听雨眠投稿
  由于计算机为了充分利用CPU的高性能,以及各个硬件存取速度巨大的差异带来的一系列问题为了充分压榨CPU的性能,CPU会对指令乱序执行或者语言的编译器会指令重排,让CPU一直工作不停歇,但同时会导致有序性问题。为了平衡CPU的寄存器和内存的速度差异,计算机的CPU增加了高速缓存,但同时导致了可见性问题为了平衡CPU与IO设备的速度差异,操作系统增加了进程、线程概念,以分时复用CPU,但同时导致了原子性问题。
  Java是最早尝试提供内存模型的编程语言。由于Java语言是跨平台的,另外各个操作系统总存在一些差异,Java在物理机器的基础上抽象出一个内存模型(JMM),来简化和管理并发程序。我们都知道Java并发的三大特性:原子性,可见性,有序性原子性指的是一个不可以被分割的操作,即这个操作在执行过程中不能被中断,要么全部不执行,要么全部执行。且一旦开始执行,不会被其他线程打断。可见性指的是一个线程修改了共享变量后,其他线程能立即感知这个变量被修改。有序性指程序按照代码的先后顺序执行。在Java内存模型中,为了提升效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响
  那么本文我们就聊聊关键字volatile,可能是Java中最微妙和最难用的关键字,看看其在Java内存模型中是如何保证并发操作的原子性、可见性、有序性的?什么是volatile关键字
  volatile是Java中用于修饰变量的关键字,其可以保证该变量的可见性以及顺序性,但是无法保证原子性。更准确地说是volatile关键字只能保证单操作的原子性,比如x1,但是无法保证复合操作的原子性,比如x
  其为Java提供了一种轻量级的同步机制:保证被volatile修饰的共享变量对所有线程总是可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。相比于synchronized关键字(synchronized通常称为重量级锁),volatile更轻量级,开销低,因为它不会引起线程上下文的切换和调度。保证可见性
  可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。我们一起来看一个例子:publicclassVisibilityTest{publicvoidchange(){System。out。println(Thread。currentThread()。getName(),已修改flagfalse);}publicvoidload(){System。out。println(Thread。currentThread()。getName(),开始执行。。。。。);inti0;while(flag){i;}System。out。println(Thread。currentThread()。getName(),结束循环);}publicstaticvoidmain(String〔〕args)throwsInterruptedException{VisibilityTesttestnewVisibilityTest();线程threadA模拟数据加载场景ThreadthreadAnewThread(()test。load(),threadA);threadA。start();让threadA执行一会儿Thread。sleep(1000);线程threadB修改共享变量flagThreadthreadBnewThread(()test。change(),threadB);threadB。start();}}
  其中:threadA负责循环,threadB负责修改共享变量flag,如果flagfalse时,threadA会结束循环,但是上面的例子会死循环!原因是threadA无法立即读取到共享变量flag修改后的值。我们只需,加上volatile关键字threadA就可以立即退出循环了。
  其中Java中的volatile关键字提供了一个功能:那就是被volatile修饰的变量P被修改后,JMM会把该线程本地内存中的这个变量P,立即强制刷新到主内存中去,导致其他线程中的volatile变量P缓存无效,也就是说其他线程使用volatile变量P在时,都是从主内存刷新的最新数据。而普通变量的值在线程间传递的时候一般是通过主内存以共享内存的方式实现的;
  因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronized和final两个关键字以及各种Lock也可以实现可见性。加锁的话,当一个线程进入synchronized代码块后,线程获取到锁,会清空本地内存,然后从主内存中拷贝共享变量的最新值到本地内存作为副本,执行代码,又将修改后的副本值刷新到主内存中,最后线程释放锁。保证有序性
  有序性,顾名思义即程序执行的顺序按照代码的先后顺序执行。但现代的计算机中CPU中为了能够让指令的执行尽可能地同时运行起来,提示计算机性能,采用了指令流水线。一个CPU指令的执行过程可以分成4个阶段:取指、译码、执行、写回。这4个阶段分别由4个独立物理执行单元来完成。
  理想的情况是:指令之间无依赖,可以使流水线的并行度最大化但是如果两条指令的前后存在依赖关系,比如数据依赖,控制依赖等,此时后一条语句就必需等到前一条指令完成后,才能开始。所以CPU为了提高流水线的运行效率,对无依赖的前后指令做适当的乱序和调度,即现代的计算机中CPU是乱序执行指令的
  另一方面,只要不会改变程序的运行结果,Java编译器是可以通过指令重排来优化性能。然而,重排可能会影响本地处理器缓存与主内存交互的方式,可能导致在多线程的情况下发生细微的BUG。
  指令重排一般可以分为如下三种类型:编译器优化重排序,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。指令级并行重排序,现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。内存系统重排序,由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。这并不是显式的将指令进行重排序,只是因为缓存的原因,让指令的执行看起来像乱序。
  从Java源代码到最终执行的指令序列,一般会经历下面三种重排序:
  变量初始化赋值
  我们一起来看一个例子,让大家体悟volatile关键字的禁止指令重排的作用:inti0;intj0;intk0;i10;j1;
  对于上面的代码我们正常的执行流程是:
  初始化i初始化j初始化ki赋值j赋值
  但由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。语句可能的执行顺序如下:
  初始化ii赋值初始化jj赋值初始化k
  指令重排对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序,提升性能。指令重排不会影响单线程的执行结果,但是会影响多线程并发执行的结果正确性。但当我们用volatile修饰变量k时:inti0;intj0;volatileintk0;i10;j1;
  这样会保证上面代码执行顺序:变量i和j的初始化,在volatileintk0之前,变量i和j的赋值操作在volatileintk0后面懒汉式单例双重校验锁volatile版
  我们可以使用volatile关键字去阻止重排volatile变量周围的读写指令,这种操作通常称为memorybarrier(内存屏障),详情可见:mp。weixin。qq。comsTyiCfVMee中懒汉式单例双重校验锁volatile版隐藏特性
  volatile关键字除了禁止指令重排的作用,还有一个特性:当线程向一个volatile变量写入时,在线程写入之前的其他所有变量(包括非volatile变量)也会刷新到主内存。当线程读取一个volatile变量时,它也会读取其他所有变量(包括非volatile变量)与volatile变量一起刷新到主内存。尽管这是一个重要的特性,但是我们不应该过于依赖这个特性,来自动使周围的变量变得volatile,若是我们想让一个变量是volatile的,我们编写程序的时候需要非常明确地用volatile关键字来修饰。无法保证原子性
  volatile关键字无法保证原子性,更准确地说是volatile关键字只能保证单操作的原子性,比如x1,但是无法保证复合操作的原子性,比如x
  所谓原子性:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换(contextswitch)int0;语句1,单操作,原子性的操作i;语句2,复合操作,非原子性的操作
  其中:语句2i其实在Java中执行过程,可以分为3步:i被从局部变量表(内存)取出,压入操作栈(寄存器),操作栈中自增使用栈顶值更新局部变量表(寄存器更新写入内存)
  执行上述3个步骤的时候是可以进行线程切换的,或者说是可以被另其他线程的这3步打断的,因此语句2不是一个原子性操作volatile版i
  我们再来看一个例子:publicclassTest1{publicstaticvoidadd(){for(inti0;i1000;i){}}publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadt1newThread(Test1::add);Threadt2newThread(Test1::add);t1。start();t2。start();t1。join();等待该线程终止t2。join();System。out。println(val);}}
  2个线程各循环2000次,每次1,如果volatile关键字能够保证原子性,预期的结果是2000,但实际结果却是:1127,而且多次执行的结果都不一样,可以发现volatile关键字无法保证原子性。synchronized版i
  我们可以利用synchronized关键字来解决上面的问题:publicclassSynchronizedTest{publicsynchronizedstaticvoidadd(){for(inti0;i1000;i){}}publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadt1newThread(SynchronizedTest::add);Threadt2newThread(SynchronizedTest::add);t1。start();t2。start();t1。join();等待该线程终止t2。join();System。out。println(val);}}
  运行结果:2000Lock版i
  我们还可以通过加锁来解决上述问题:publicclassLockTest{staticLocklocknewReentrantLock();publicstaticvoidadd(){for(inti0;i1000;i){lock。lock();上锁try{}catch(Exceptione){e。printStackTrace();}finally{lock。unlock();解锁}}}publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadt1newThread(LockTest::add);Threadt2newThread(LockTest::add);t1。start();t2。start();t1。join();等待该线程终止t2。join();System。out。println(val);}}
  运行结果:2000Atomic版i
  Java从JDK1。5开始提供了java。util。concurrent。atomic包(以下简称Atomic包),这个包中的原子操作类,靠CAS循环的方式来保证其原子性,是一种用法简单、性能高效、线程安全地更新一个变量的方式。
  这些类可以保证多线程环境下,当某个线程在执行atomic的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行。
  我们来用atomic包来解决volatile原子性的问题:publicclassAtomicTest{publicstaticAtomicIntegervalnewAtomicInteger();publicstaticvoidadd(){for(inti0;i1000;i){val。getAndIncrement();}}publicstaticvoidmain(String〔〕args)throwsInterruptedException{Threadt1newThread(AtomicTest::add);Threadt2newThread(AtomicTest::add);t1。start();t2。start();t1。join();等待该线程终止t2。join();System。out。println(val);}}
  运行结果:2000,如果我们维护现有的项目,如果遇到volatile变量最好将其替换为Atomic变量,除非你真的特别了解volatile。Atomic就不展开说了,先挖个坑,以后补上volatile原理
  当大家仔细读完上文的懒汉式单例双重校验锁volatile版,会发现volatile关键字修饰变量后,我们反汇编后会发现多出了lock前缀指令,lock前缀指令在汇编中LOCK指令前缀功能如下:被修饰的汇编指令成为原子的与被修饰的汇编指令一起提供内存屏障效果(lock指令可不是内存屏障)
  内存屏障主要分类:一类是可以强制读取主内存,强制刷新主内存的内存屏障,叫做Load屏障和Store屏障另一类是禁止指令重排序的内存屏障,主要有四个分别叫做LoadLoad屏障、StoreStore屏障、LoadStore屏障、StoreLoad屏障
  这4个屏障具体作用:LoadLoad屏障:(指令Load1;LoadLLoad2),在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。LoadStore屏障:(指令Load1;LoadSStore2),在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。StoreStore屏障:(指令Store1;StoreSStore2),在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。StoreLoad屏障:(指令Store1;StoreLLoad2),在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
  对于volatile操作而言,其操作步骤如下:每个volatile写入之前,插入一个StoreStore,写入以后插入一个StoreLoad每个volatile读取之前,插入一个LoadLoad,读取之后插入一个LoadStore
  我们再总结以下,用volatile关键字修饰变量后,主要发生的变化有哪些?:当一个线程修改了volatile修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。即volatile关键字保证了并发的可见性
  使用volatile关键字修饰共享变量后,每个线程要操作该变量时会从主内存中将变量拷贝到本地内存作为副本,但当线程操作完变量副本,会强制将修改的值立即写入主内存中。然后通过CPU总线嗅探机制告知其他线程中该变量副本全部失效,(在CPU层,一个处理器的缓存回写到内存会导致其他处理器的缓存行无效),若其他线程需要该变量,必须重新从主内存中读取。在x86的架构中,volatile关键字底层含有lock前缀的指令,与被修饰的汇编指令一起提供内存屏障效果,禁止了指令重排序,保证了并发的有序性
  确保一些特定操作执行的顺序,让cpu必须按照顺序执行指令,即当指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;volatile关键字无法保证原子性,更准确地说是volatile关键字只能保证单操作的原子性,比如x1,但是无法保证复合操作的原子性,比如x。有人可能问赋值操作是原子操作,本来就是原子性的,用volatile修饰有什么意义?在Java数据类型足够大的情况下(在Java中long和double类型都是64位),写入变量的过程分两步进行,就会发生Wordtearing(字分裂)情况。JVM被允许将64位数量的读写作为两个单独的32位操作执行,这增加了在读写过程中发生上下文切换的可能性,多线程的情况下可能会出现值会被破坏的情况
  在缺乏任何其他保护的情况下,用volatile修饰符定义一个long或double变量,可阻止字分裂情况
投诉 评论 转载

红枣不补铁!真正的补铁好物,确定不来看看?铁元素是合成人体血液必需原料,缺铁的话容易导致贫血。说到补血的食物,大家第一反应是不是红枣,红枣虽然是红的,可它真的不补铁。这些补铁秘方可信吗?首先大家得知道成年女……电脑如何分屏设置?分屏是同一个电脑屏幕上显示不同的内容,让用户在工作时不局限于一个屏幕。手机分屏很多人都会操作,但是电脑分屏知道操作方法的人就寥寥无几了。今天小编就来跟大家说说电脑怎么分屏的步骤……切尔西不满巴萨,夏转两队恩怨频发,图赫尔明言,想买人需出高价切尔西主帅图赫尔最近的一句发言,直接把切尔西和巴萨之间的矛盾,推上了舆论的风口浪尖。天空体育的记者在提问切尔西球员阿兹利奎塔加盟巴萨的转会生气是否生气时,图赫尔直言也许是有一些……一路风光一路故事聞鍾記郵(4263)一路风光一路故事说起黄河,中国人是不陌生的,因为黄河自古就被誉为母亲河,中华文明的源头之一就是黄河流域,另一个源头则是长江流域。几千年来,长江与黄河孕……给演反派最好的10位港片演员排个名万梓良仅第5,吴镇宇第4上世纪8090年代,是香港电影的鼎盛时期,诞生了无数经典作品。香港电影之所以有长达20年的辉煌,离不开一众巨星的大放异彩,当然,也离不开绿叶们的帮衬。而这些绿叶中就……volatile关键字在并发中有哪些作用?由于计算机为了充分利用CPU的高性能,以及各个硬件存取速度巨大的差异带来的一系列问题为了充分压榨CPU的性能,CPU会对指令乱序执行或者语言的编译器会指令重排,让CPU一直工作……赵昭仪优异成绩考入浙江传媒大学,清甜美丽长相1999年5月21日出生于辽宁省大连市,就读于浙江传媒学院播音主持专业,中国内地女演员。图片来源网络2019年7月17日,主演的青春校园剧《不可思议的晴朗》播出;1……假如再回到2019年以前疫情三年,终于在这几天结束了,不是真的结束,而是重新给了我们自由。管控期间,我们足不出户,好想出去走走,居家的日子真心难熬。如今,各方面不受管制,可以随意走,大家更加担心……莱莎联动特殊设备详细数据与弹幕演示炙烈炎烧适用舰种:通用机动10(13)雷击35(41)赋予火伤莱莎琳斯托特装备时,战斗开始后10秒,触发一轮特殊攻击(威力依据炮击属性),对命中的敌人施……悲痛!前中国女篮主帅吕长新离世,曾率队夺亚锦赛冠军奥运第6名北京时间5月27日,对于中国篮球来说在这个夏天非常不平静,我们看到了众多中国篮球的有志青年纷纷赴美特训冲击美职联的舞台,看到了中国女篮两大内线巨星李月汝和韩旭也都登上了WNBA……华宇软件高风险高收益并存的投机品种,买入逻辑解析在近期信创概念强势时,账户中买入了一只纯粹的信创概念股华宇软件。然而这只股票,并不准备分配太多的资金。其高风险与高收益并存,并且属于政策性周期股,这种纯粹博弈困境反转的品……B社母公司ZenimaxQA团队宣布将投票成立工会如果大多数符合条件的员工投票赞成,这将是微软的第一个工会,也将成为美国最大的游戏行业工人工会(考虑到目前微软仍未完成对动视暴雪的收购,动视RavenSoftware的QA工会暂……
中超第一轮我的盘点及第二轮比赛结果的预测魔术师约翰逊透露辞职原因一个新的轮回蒸鸡蛋羹加冷水温水还是开水?蒸几分钟?告诉方法,滑嫩如豆腐脑夜兰胡桃雷电将军轮流up,平民玩家选谁最保值?大佬别考虑胡桃dnf风云崛起任务怎么完成各让一步!美记预测火箭三方交易赚6个选秀权,76人2换1省几世界足坛身价最高十一人阵容出炉曼城与利物浦球员入选中国队缺席世界杯,但中国没有!荣耀X40GT官宣,号称性能越级标杆,搭载骁龙888处理器?击败荣耀和iQOO,蝉联2K销量榜第一名,12256GB大内拉皮手术不靠谱?想做拉皮变年轻,没想到耳朵却变了形
消息称英特尔将访问台积电,以求避免与苹果竞争3nm芯片产能光滑反义词是什么呢央行降息个人购汇放宽,俄罗斯打出金融组合拳5款好用还便宜的国货国服要来?彩虹六号围攻更新惊现企鹅和Wegame挂饰实现碳达峰碳中和这场硬仗怎么打看了万物理论感叹霍金的一生!安徽著名养生宜居县,是首批九大天然氧吧之一,入选中国最美县域奶瓶到水杯如何过渡奶瓶到水杯怎么过渡趣味科普大谣言盘点第一次潜水危险吗银行服务的座右铭

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找江西南阳嘉兴昆明铜陵滨州广东西昌常德梅州兰州阳江运城金华广西萍乡大理重庆诸暨泉州安庆南充武汉辽宁