暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JAVA中的无锁(一)

于果1023 2019-04-16
284

相比于java中的锁的机制来说,无锁的方式会增加更多的性能,使用锁的话,据统计做一次上下文的检索,需要8万次的使用周期,但是如果使用无锁的重试的操作,最快的话,就可能达到10以下的使用周期,这就是无锁带来的好处,下面就无锁来做更多的介绍


无锁使用了一个叫做CAS的算法,CAS全名叫做“Compare And Swap”,是“比较和交换”的意思,这里面就一个随机选举的策略在里面,在我们通常使用的sychronized中,是通过阻塞的方式来保证线程的安全,而无锁中多个线程同时进入的时候,只能保证一个线程能够成功的,这是通过一个期望值和实际值相同,那么就可以设置值,当期望值和实际值不相同的时候,那么就代表值已经被修改过了,那么如果想要修改的话,那么就要等到下一次修改了


但是有些人,认为CAS中是有bug存在的,比如说,第一步是读,第二布是写,第三步才是设置值,那么就有可能有问题,在读完之后,设置值之前其他线程把值设置了,那么就会出现冲突。但实际上这种担心是多余的,整个CAS操作过程,这是一个原子操作,它是一条CPU指令完成的


下面说几个无锁的使用:

1、AtomicInteger

它的路径是如下图:

这里的话,先创建一个atomicInteger的demo


跟进一下源码中,可以看到

这个里面的其实实际上操作的是value这个值

这个类里的方法的介绍

这里重点描述下compareAndSet这个方法


从这个方法中可以看到,expect是期望值,而update是要更新的值,这个方法返回的是boolean值,当期望值和之前的值相同的话,那么就返回true,否则返回false


其中的this表示的是本类的对象,valueOffset表示的是偏移量

而unsafe这个操作是类似于C语言,这是提供给jdk的方法,是非公开的,如果想要看到其中的源码,那还得动点手脚


下面再说一下这个atomicInteger的具体使用

这边的话写了一个测似的类,这个类中对i变量做了累加的操作,最后得出的结果是

但是如果是用普通的int i来做操作的话

结果就是

第一个是线程安全的,第二个是线程不安全的


2、AtomicReference

这个同AtomicInteger的用法类似,只是这个是对引用来使用的

相对来说的话,这个多了一个泛型的限制

这里就通过一个demo来做介绍

这里设置一个变量指向abc的地址,然后再调用compareAndSet的方法来重新设置值,这里开启的是10个线程,但是最后的话只有一个线程能设置成功

3、AtomicStampedReference

说这个之前先说一下戳的这个概念,也就是Stamped,这个就是用来做标识,或者说是一个递增或者递减,保持事件唯一性的一个操作,接下来再讲一下A->B->A的问题

例如:

A原先有一个值是10,这个时候我想把A里的值设置为11,在我还没有设置值之前,有一个B把这个值设置成了12,然后又有设置成了10,但是这时A再通过CAS算法的时候,发现A还是10,那么这个时候,就能正常的去设置值,这个情况对加减来说的话,没有任何的影响,因为这个只要求结果,不要求过程。但是如果对要求过程变化的来说的话,就会出现问题了


比如说,当有地方做活动的时候,当你本金有10块钱的时候,就给你充值20块,但是只有一次的机会,等你花完20块,还剩下10块的时候,就没有充值了,所以只做一次充值的机会


接下来说一下这个AtomicStampedReference内部的实现

内部其实对value有了一个封装,这个封装就时pair,这个pair里面就有关于“时间戳”(stamp)的概念

这里主要讲一下compareAndSet这个方法

那这个方法什么时候能设置成功呢,一个时是当前的值与期望的值相同的时候并且期望的stamp值也是相同的话,如果说新的设置值与当前值相同而且新的时间戳和当前的时间戳相同的话,也算是成功。或者是casPair的操作能成功的话也算是成功


casPair的这个操作在jdk中是极其常见的,casPair可以更新链表的头部或者是尾部啊什么的


这里有写一个demo

这个demo产生的结果就是

这就只有一次的充值机会

但是如果说我修改了时间戳的话,结果就会不同

保持两个时间戳相同的话

那么就会一直充值下去,这就变成了普通的CAS操作了

文章转载自于果1023,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论