java 锁记录,java学习记录---CAS乐观锁

CAS,全称 Compare And Swap (比较与交换),是一种乐观锁,同样是锁相比 synchronized性能却要高不少,因为是 synchronized阻塞的,而CAS是非阻塞的。CAS主要有3个操作数,内存值V,预期的旧值A,需要修改的新值B,可以这样理解:在V的地址把A改成B,当V位置的值与预期的旧值A相同时候,则修改成B,否则不更新。

下面看个图简单理解一下CAS:当线程1和线程2同时操作内存V,线程1想要把内存V的变量值从A(2)改成B(1)而线程2想要把V的变量值从A(2)改成B(3)。假设这个时候是线程1优先抢到资源所以线程1先进行CAS操作,这个时候预期旧值2是相等的则执行了更新,更新完后内存V的变量值就变成1,这个时候线程2才进入比较预期的A值与V中实际的变量值已经不相同了,所以更新失败。

98f56c530b8ce4edcbc45fda5af9560c.png

这个图看上去是Compare And Swap 是同时操作,但实际上是分2部执行:1.比较(compare),2.交换(swap),它的原子性是通过硬件实现的,而不是我们java代码实现

java提供的CAS操作类

60cfd36e4cec57efc5ae247ae021d22c.png

我们随便找其中一个Atomic类学习

当V的值与A相等则更新成功

public static voidmain(String[] args) {

//定义Integer的CAS类AtomicInteger AI = newAtomicInteger();

//设置初始化值AI.set(1);

//将1替换成2并返回是否替换成功,该函数第一个参数为预期的旧值(A),第二个参数是需要修改的值(B)booleanb = AI.compareAndSet(1, 2);

//打印是否替换成功System.out.println(b);

//打印最新值System.out.println(AI);

}

打印结果:

54208b95fe1f771a6bb056f7efac2beb.png

当V的值与A不相等则更新失败

public static voidmain(String[] args) {

//定义Integer的CAS类AtomicInteger AI = newAtomicInteger();

//设置初始化值AI.set(1);

//将1替换成2 并返回是否替换成功,该函数第一个参数为预期的旧值(A),第二个参数是需要修改的值(B)booleanb = AI.compareAndSet(2, 2);

//打印是否替换成功System.out.println(b);

//打印最新值System.out.println(AI);

}

打印结果:

39a7fd2a6aa7da61841ca4af7d37655a.png

这2个打印结果的结论也证实了最开始那个图的原理,只有V的变量值与A相同时候,才会修改成B ,

接下来看看原子性

普通的int类型

public classAmIntegerTest implementsRunnable {

private static volatile intI= 0;

public voidrun() {

//每个线程自增100000次,for(inti = 0; i++ < 100000; add()) {}

//线程执行完之后结果System.out.println(Thread.currentThread().getName() + ":"+ I);

}

public static voidadd(){

I++;

}

public static voidmain(String[] args) {

AmIntegerTest ait = newAmIntegerTest();

Thread t1 = newThread(ait);

Thread t2 = newThread(ait);

t1.start();

t2.start();

}

}

打印结果显示,普通的int没有原子性

14a8326c11c18a6f43ff4400d60ce435.png

除非加上synchronized关键字,接下来改造add()添加synchronized其他代码不变

public synchronized static voidadd(){

I++;

}

无论执行多少次最终结果都是200000,可以保证原子性

d5cc442dc6b0d4d49674543361b76aff.png

我们再看看java提供的CAS操作类

public classAmIntegerTest implementsRunnable {

//定义Integer的CAS类private staticAtomicInteger AI= newAtomicInteger();

public voidrun() {

//每个线程自增100000次,for(inti = 0;i++ < 100000; AI.incrementAndGet()) {}

//线程执行完之后结果System.out.println(Thread.currentThread().getName() + ":"+ AI.get());

}

public static voidmain(String[] args) {

AmIntegerTest ait = newAmIntegerTest();

Thread t1 = newThread(ait);

Thread t2 = newThread(ait);

t1.start();

t2.start();

}

}

无论执行多少次最终结果都是200000,所以是具有原子性的,但是它的原子性是其他语言实现的,这里就不讨论它的实现原理了(我不会C++)

c74fc74f444f034dcad856e97b424f47.png

AtomicInteger.value是一个volatile修饰的变量(内存锁定,同一时刻只有一个线程可以修改内存值)

private volatile intvalue;

AtomicInteger.incrementAndGet()这是一个自增函数,实现了自旋锁(无限循环),下面看看incrementAndGet()的源代码,它调用了Unsafe 类的getAndAddInt()。而getAndAddInt()是无限循环,直到值修改成功才结束,否则一直循环

public final intincrementAndGet() {

returnunsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

private static finalUnsafe unsafe= Unsafe.getUnsafe();

public final intgetAndAddInt(Object var1, longvar2, intvar4) {

intvar5;

do{

var5 = this.getIntVolatile(var1, var2);

} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

returnvar5;

}

getAndAddInt()实现的自旋锁原理就是内存V的变量值与A不一致时候,再重新获取V的变量值,直到V的变量值与A一致时候,才更新成B并结束,这样有个缺点就是如果自旋次数太多,会造成很大的资源消耗

在Atomic包中的CAS操作都是基于以下3个方法实现,Unsafe类里面的所有方法都是 native 声明的,说明是调用其他语言实现的

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。public final native booleancompareAndSwapObject(Object o, longoffset,Object expected, Object x);

public final native booleancompareAndSwapInt(Object o, longoffset,intexpected,intx);

public final native booleancompareAndSwapLong(Object o, longoffset,longexpected,longx);

我们试试使用一下这些函数

public classCasTest {

//定义java的CAS类Unsafepublic staticUnsafe U= getUnsafe();

public static voidmain(String[] args) throwsNoSuchFieldException {

// java Unsafe类调用C++实现的CAS//创建一个我们的测试对象Cas cas = newCas("1");//获取对象内value存储对象的地址longoffset = U.objectFieldOffset(cas.getClass().getDeclaredField("value"));//通过Unsafe类的CAS函数进行修改System.out.println(U.compareAndSwapObject(cas,offset,"1","2"));System.out.println(cas);}

static classCas{

privateString value;

publicCas(String value) {

this.value= value;}

@OverridepublicString toString() {

return"Cas{"+

"value='"+ value+ '\''+

'}';}

}

//因为Unsafe的构造函数是私有的,而且它提供的getUnsafe()也只有系统类才能使用,//所以我们只能通过反射获取Unsafe实例了private staticUnsafe getUnsafe() {

Unsafe unsafe = null;try{

Constructor> declaredConstructor = Unsafe.class.getDeclaredConstructors()[0];declaredConstructor.setAccessible(true);unsafe = (Unsafe) declaredConstructor.newInstance();} catch(Exception e) { e.printStackTrace();}

returnunsafe;}

}

看一下main()的打印结果,修改cas对象的value值,从“1”改成“2”,执行成功

0570e6f090576dfc9e08d4bef84aab24.png

改一下main(),试一下从“2”改成“2”

public static voidmain(String[] args) throwsNoSuchFieldException {

// java Unsafe类调用C++实现的CAS//创建一个我们的测试对象Cas cas = newCas("1");//获取对象内value存储对象的地址longoffset = U.objectFieldOffset(cas.getClass().getDeclaredField("value"));//通过Unsafe类的CAS函数进行修改System.out.println(U.compareAndSwapObject(cas,offset,"2","2"));System.out.println(cas);}

这个时候修改是失败的,因为value的值为“1”,而CAS操作里 预期的旧值是“2”,所以无法成功执行从“2”改成“2”

763f8c6d7c5ecb2d50d9f22c0ac918ab.png

下面2个函数都是Unsafe类里面的,都是私有的,而且它提供的getUnsafe()也只有系统类才能使用,所以需要通过反射获取实例

privateUnsafe() {

}

@CallerSensitive

public staticUnsafe getUnsafe() {

Class var0 = Reflection.getCallerClass();if(!VM.isSystemDomainLoader(var0.getClassLoader())) {

throw newSecurityException("Unsafe");} else{

returntheUnsafe;}

}

ABA问题

ABA就是内存V的变量值从A变成B,再从B变成A,这个时候CAS只判断值是否相等,只要值相等就会认为这个值没改变过,但实际上是已经变化了。我们可以添加多一个标识(版本号、时间)判断这个值是否已经改变过,java也提供了相关的解决方案 AtomicStampedReference 类。

先看一下普通CAS的操作类

public static voidmain(String[] args) {

//定义Integer的CAS类AtomicInteger AI = newAtomicInteger(1);

System.out.println("初始化值:"+ AI);

AI.compareAndSet(1,2);

System.out.println("第一次CAS操作完成之后值:"+ AI);

AI.compareAndSet(2,1);

System.out.println("第二次CAS操作完成之后值:"+ AI);

AI.compareAndSet(1,3);

System.out.println("第二次CAS操作完成之后值:"+ AI);

}

这种只需要V的值与A一致就可以修改,存在ABA问题

4a8e1cfceca8894b4a01d88710c4fa5f.png

接下来看看CAS的标识引用类

public static voidmain(String[] args) {

//定义AtomicStampedReference类,第一个参数是我们的初始化值,第二个是版本标识AtomicStampedReference ASR = newAtomicStampedReference(1,1);

System.out.println("初始化值:"+ ASR.getReference());

ASR.compareAndSet(1,2, ASR.getStamp(), ASR.getStamp() + 1);

System.out.println("第一次CAS操作完成之后值:"+ ASR.getReference());

//获取stamp ,下面2次新增都使用这个stamp值intstamp = ASR.getStamp();

//这一次可以修改成功,因为stamp的预期值一致ASR.compareAndSet(2,1, stamp, stamp + 1);

System.out.println("第二次CAS操作完成之后值:"+ ASR.getReference());

//这一次可以修改失败,因为stamp的预期值不一致ASR.compareAndSet(1,3, stamp, stamp + 1);

System.out.println("第三次CAS操作完成之后值:"+ ASR.getReference());

}

这种不紧V的值要与A相等,且记录的标识也需要一致才能完成修改

5da91b4da0e96f09f293fb5f84ddcee8.png

CAS是乐观锁,synchronized是悲观锁。两者各有优异,应对不同场景选择合适的锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值