CAS

CAS是指Compare And Swap,比较并交换,是一种很重要的同步思想。如果主内存的值跟期望值一样,那么就进行修改,否则一直重试,直到一致为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.concurrent.atomic.AtomicInteger;

/*
* 1、CAS是什么? ==>compareAndSet
* 比较并交换
* */
public class CASDemo {
public static void main(String[] args){
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data: "+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,1024)+"\t current data: "+atomicInteger.get());
}
}

CAS底层原理

1
2
3
4
5
6
7
8
9
10
11
public final int getAndIncrement(){
return unsafe.getAndAddInt(this,valueOffset,1);
}

public final int getAnddAddInt(Object var1,long var2,int var4){
int var5;
do{
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

this.getIntVolatile 获取主内存变量值,是个 native 方法,其目的是获取 var1 在 var2 偏移量的值,其中 var1 就是 AtomicInteger, var2 是 valueOffset 值。

那么 CAS 核心重点来了,compareAndSwapInt 就是实现 CAS 的核心方法,其原理是如果 var1 中的 value 值和 var5 相等,就证明没有其他线程改变过这个变量,那么就把 value 值更新为 var5 + var4,其中 var4 是更新的增量值,反之则继续以自旋方式继续进行操作

Unsafe

是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

2.变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

3.变量value用volatile修饰,保证了多线程之间的内存可见性。

CAS缺点

CAS实际上是一种自旋锁

  1. 一直循环,开销比较大。
  2. 只能保证一个变量的原子操作,多个变量依然要加锁。
    当对一个共享变量执行操作时,我们只能使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
  3. ABA问题

ABA问题

CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。

常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。这就引出了AtomicReference原子引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/*
* ABA问题的解决 AtomicStampedReference
* */
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);


public static void main(String[] args){
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();

new Thread(()->{
// 暂停1秒钟线程2,保证上面t1线程完成一次ABA操作
try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
System.out.println(atomicReference.compareAndSet(100,2019)+"\t"+atomicReference.get());
},"t2").start();

try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}

System.out.println("======以下是ABA问题的解决=====");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第1次版本号:"+stamp);

// 暂停1秒钟t3线程
try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第3次版本号:"+atomicStampedReference.getStamp());
},"t3").start();

new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第1次版本号:"+stamp);

// 暂停1秒钟t4线程
try{TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,atomicStampedReference.getStamp()+1);

System.out.println(Thread.currentThread().getName()+"\t修改成功否: "+result+"\t当前最新实际版本号:"+atomicStampedReference.getStamp());

},"t4").start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javax.jws.soap.SOAPBinding;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

class User{
String userName;
int age;

public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
}
public class AtomicReferenceDemo {
public static void main(String[] args){
AtomicReference<User> atomicReference = new AtomicReference<>();

User z3 = new User("z3",22);
User li4 = new User("li4",25);

atomicReference.set(z3);
z3 = new User("wang5", 44);
System.out.println(atomicReference.compareAndSet(z3,li4)+"\t"+atomicReference.get().toString());

}
}