javaee论坛

普通会员

225648

帖子

335

回复

349

积分

楼主
发表于 2019-11-03 06:34:26 | 查看: 308 | 回复: 1

进入今天的主题先看一段代码和代码的输出结果

classAtomicDemoimplementsRunnable{privateintserialNumber=0;publicintgetSerialNumber(){returnserialNumber++;}@Overridepublicvoidrun(){try{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(getSerialNumber());}}publicclassTestAutomic{publicstaticvoidmain(String[]args){AtomicDemoatomicDemo=newAtomicDemo();for(inti=0;i<10;i++){newThread(atomicDemo).start();}}}

可以看到在多线程的情况下serialNumber++操作出现了问题,分析下为什么会出现这个问题?

主内存和工作内存的区别:根据JMM的设计,系统存在一个主内存(MainMemory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(WorkingMemory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

由于serialNumber++操作,会在内存中产生一个临时的变量temp,首先将temp=serialNumber,然后serialNumber=serialNumber+1操作,最后serialNumber=1去更新主存的值;但是在多线程并发的情况下,线程2拿到的serialNumber的值可能是0,可能不是serialNumber=serialNumber+1操作后的值。由于原子性的操作是不可以分割的,但是++的操作把serialNumber++分割了,在多线程的情况下,所以出现了这种情况。在Volatile关键字内存可见的情况下,输出的结果还是一样的,因为Volatile关键字不能保证变量状态的“原子性操作”,怎么解决这个问题,先看2个概念。

CAS算法:

CAS(Compare-And-Swap)是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。CAS是一种无锁的非阻塞算法的实现。CAS包含了3个操作数:需要读写的内存值V进行比较的值A拟写入的新值B当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作。

原子变量:

类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将volatile值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。类AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供volatile访问语义方面也引人注目,这对于普通数组来说是不受支持的。核心方法:booleancompareAndSet(expectedValue,updateValue)java.util.concurrent.atomic包下提供了一些原子操作的常用类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicReferenceAtomicIntegerArray、AtomicLongArrayAtomicMarkableReferenceAtomicReferenceArrayAtomicStampedReference

看上图的下方的左边部分,V=0拿到是主存的值,A=0是作为比较的值,B=1是在当前的线程1,V+1之后之后的值(通过B来更新主存的值),右边部分V=0在多线程的情况下拿到的可能是0,A=1是在线程1(多线程的情况下),+1之后改之后的值,B=1是当前的线程2,V+1之后的值,V!=A,此时不能拿B去更新主存的值,说白了就是在多个线程的情况下,只有一个线程会执行成功。但是由于CAS是一种无锁的非阻塞算法的实现,压根不会放弃CPU给它的执行权,执行的效率非常的高,又会去执行一遍,取主存拿值,然后在计算更新值。这个要清楚的是CAS算法是一种硬件对并发的支持(看概念)。

修改后的代码

classAtomicDemoimplementsRunnable{//privateintserialNumber=0;privateAtomicIntegeratomicInteger=newAtomicInteger();publicintgetSerialNumber(){returnatomicInteger.getAndIncrement();//自增加1}@Overridepublicvoidrun(){try{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(getSerialNumber());}}publicclassTestAutomic{publicstaticvoidmain(String[]args){AtomicDemoatomicDemo=newAtomicDemo();for(inti=0;i<10;i++){newThread(atomicDemo).start();}}}

原子变量保证了数据的原子性,volatile关键字内存可见性。

说到最后,到底CAS算法是怎么实现的?下篇博客介绍。


普通会员

0

帖子

336

回复

347

积分
沙发
发表于 2023-11-21 09:53:29

围观

您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017