一、CAS的使用
1.什么是CAS?
CAS全称呼Compare-And-Swap,它是一条CPU并发原语,俗称比较并交换!下面是借助AtomicInteger
类进行说明
package Test;
import java.util.concurrent.atomic.AtomicInteger;
public class A {
public static void main(String[] args) {
checkCAS();
}
public static void checkCAS(){
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data is " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2014) + "\t current data is " + atomicInteger.get());
}
}
2.JDK自带的自旋锁
如下是 UnSafe
类中的某个方法,可以看出用到了CAS自旋锁
var1 AtomicInteger对象本身
var2 该对象的引用地址
var4 需要变动的数据
var5 通过var1 var2找出的主内存中真实的值
用该对象前的值与var5比较;
如果相同,更新var5+var4并且返回true,
如果不同,继续去之然后再比较,直到更新完成
该方法有个do while循环,如果CAS失败,则一直会进行尝试,直到成功为止。
3.手写的自旋锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 实现自旋锁
* 自旋锁好处,循环比较获取知道成功位置,没有类似wait的阻塞
*
* 通过CAS操作完成自旋锁,A线程先进来调用mylock方法自己持有锁5秒钟,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,知道A释放锁后B随后抢到
*/
public class SpinLockDemo {
public static void main(String[] args) {//main方法用于测试
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.mylock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "Thread 1").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.mylock();
spinLockDemo.myUnlock();
}, "Thread 2").start();
}
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void mylock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in");
while (!atomicReference.compareAndSet(null, thread)) {//如果为null则换成当前线程
}
}
public void myUnlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()");
}
}
说一下原理:
1.Thread1这个线程先启动,执行了
mylock
方法将atomicReference的值设为Thread1
2.由于sleep
方法的存在,Thread1进入休眠状态
3.Thread2这个线程紧接着启动,由于atomicReference的值非空,Thread2会卡死在mylock
方法的while循环
4.直到Thread1执行了unlock
方法,将atomicReference的值设为Null,Thread2才会跳出mylock
方法的while循环
4.自旋锁的缺点
1.循环时间长,开销大
例如getAndAddInt方法执行,有个do while循环,如果CAS失败,一直会进行尝试,如果CAS长时间不成功,可能会给CPU带来很大的开销
2. 只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性
3. ABA问题
CAS算法实现一个重要前提需要去除内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如线程1从内存位置V取出A,线程2同时也从内存取出A,并且线程2进行一些操作将值改为B,然后线程2又将V位置数据改成A,这时候线程1进行CAS操作发现内存中的值依然时A,然后线程1操作成功。
ABA问题的代码展示:
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) {
System.out.println("=====以下时ABA问题的产生=====");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "Thread 1").start();
new Thread(() -> {
try {
//保证线程1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "Thread 2").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);
try {
TimeUnit.SECONDS.sleep(2);
} 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());
}, "Thread 3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference());
}, "Thread 4").start();
}
}
二、源码级分析
1.JDK层次的源码我们不断点进去,追到了compareAndSwapInt
这个native方法!
2.如果想继续往下看就得下载openJDK,看unsafe.cpp
文件的内容!
继续查看atomic_linux_x86_inline.hpp
注意啊,Lock_IF_MP
可以从下面看出主要是有个lock操作!
其中可以查看os.cpp
了解一下os::is_MP
方法
最终结论:cas的最底层指令是lock cmpxchg
,lock的最终实现比较复杂,可以锁缓存,锁总线。
可以发现cas乐观锁的最底层依旧是悲观的,因为需要使用lock