CAS是java中的一种乐观锁实现方式,它能在很多场景下保证线程安全的同时有着很好的性能。
先看一个例子
package cas;
public class CASdemo {
public static int count=0;
public static void main(String[] args) {
for(int i=0;i<2;i++) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int j=0;j<1000;j++) {
count++;
}
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
我们同时开启两个线程对共享变量进行自加1000的操作,那么很明显它存在线程安全问题结果不为2000。
我们常见的解决方案是加上Synchronized关键字来保证线程安全。
package cas;
public class CASdemo {
public static int count=0;
public static void main(String[] args) {
for(int i=0;i<2;i++) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int j=0;j<1000;j++) {
synchronized(CASdemo.class){ //加锁
count++;
}
}
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
这时可以看结果为2000,说明synchronized保证了线程安全。
但是synchronized实现线程安全有一个很大的弊端就是性能问题。 使用Synchronized关键字时没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,性能代价比较高。
这个时候CAS机制就能很好的起到效果,java提供了java.util.concurrent.atomic包下一系列的原子操作类,它们都是实现了CAS机制。能保证原子性操作
package cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASdemo {
public static AtomicInteger count=new AtomicInteger(0); //使用原子操作类
public static void main(String[] args) {
for(int i=0;i<2;i++) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int j=0;j<1000;j++) {
count.incrementAndGet(); //调用自增方法
}
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
可以看到它也保证了线程安全
那么CAS机制到底是怎样的?
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。当我们 更新一个变量的时候,只有当内存地址V对应的实际值与旧的预期值A相等时,才会将值替换为B
假如内存地址V中有一个值为1.
现在有一个线程要对它执行加一操作,那么对于这个线程的A=1,B=2。而此时在在它提交更新之前,另一个线程抢先将值加为了2.。那么此时
这个时候第一个线程提交更新,开始比较A值,发现不相等,提交失败。之后这个线程会自旋。重新得到A值,计算B值。那么就变成了
A=2,B=3
再一次进行比较,发现A值相等,提交成功
如果此时又有一个线程抢先改变了A值,那么它就会继续自旋!
综上所述,可以总结,synchronized本质上是悲观锁,总是认为存在线程安全问题。CAS为乐观锁,总是认为线程安全不严重,所以compare and swap。
在java中,使用了CAS机制的有J.U.C下的Atomic包下的原子操作类,lock系列类。在jdk1.6之后,synchronized为了改善性能有个从偏向锁,到轻量锁,到重量锁的过程。在变为重量锁之前也是使用CAS机制。
当然CAS机制也存在一些缺点:因为它需要不断的去尝试更新,所以在高并发情况下,会占用CPU大量资源。它只能保证一个变量的原子性。还有一个更严重的问题ABA问题。
ABA问题
假如内存地址V上有一个值A
现在有两个线程要修改它为B值,此时线程1,2都获取了它的值,并且线程1先完成了修改,线程2阻塞住了。此时又进来一个线程3,此时状态大概为
线程3执行成功,值修改为A了,此时线程2仍然阻塞。所以此时,线程2并不知道值已经从A-》B又从B-》A。
这就是ABA问题,在实际场景中,会造成线程不安全!!
ABA问题的解决方法
加一个版本号,不止比较值A,还比较版本号,每提交一次更新操作版本号就加一。java中AtomicStampedReference就是利用 版本号解决了这个问题。
tip:具体如何底层如何实现的CAS可以看看源码,是使用了unsafe提供的原子性操作。