什么是CAS?
CAS 是英文单词Compare And Swap的缩写,翻译过来就是比较并交换的意思。
CAS中有三个基本的操作数:内存地址V、旧的预期值A、要修改的值B
,如果要更新一个变量的时候,只有当内存地址V == 旧的预期值A,也就是当且仅当:变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B
这样说可能有些抽象,下面看一张图:
CAS的缺点:
- CPU开销大
在并发量很高的情况下,如果有多个线程反复的尝试去更新某一个新的变量,却一直不能更新成功的时候,循环往复,会给CPU带来很大的压力。 - 不能保证代码块的原子性,只能保证一个变量的原子操作。
- ABA的问题
这是CAS机制最大的问题所在
代码示例
//代码一:下面这段代码最后打印的结果有可能会小于200
public class CountTest{
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(10);
}catch(InterruptedException e){
e.printStackTrace();
}
//在每个线程中,让count的值自增100次
for(int j= 0;j<100;j++){
count ++;
}
}
}
).start();
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.print("count= "+count);
}
}
//代码二(加"synchronized"):下面这段代码最后打印的结果一定是200
public class CountTest{
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(10);
}catch(InterruptedException e){
e.printStackTrace();
}
//在每个线程中,让count的值自增100次
for(int j= 0;j<100;j++){
synchronized(CountTest.class){
count ++;
}
}
}
}
).start();
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.print("count= "+count);
}
}
//代码三(使用"CAS"操作):下面这段代码最后打印的结果一定是200
所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
public class CountTest{
//public static int count = 0;
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(10);
}catch(InterruptedException e){
e.printStackTrace();
}
//在每个线程中,让count的值自增100次
for(int j= 0;j<100;j++){
count.increamentAndGet();
}
}
}
).start();
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.print("count= "+count);
}
}
这两种方式的性能比较:
Synchronized
关键字会让没有得到锁的线程进入BLOCKED
状态,而后在争夺到锁资源后恢复为RUNNINGABLE
状态,这个过程中涉及到操作系统用户模式 和 内核模式
间来回切换,这样的切换代价很高,尽管在jdk1.6
以后 Synchronized
关键字做了优化,增加了从偏向锁到轻量级锁再到重量级锁
的过渡,但是在最终转变为重量级锁之后,性能仍然很低。