深入理解CAS
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的旧值,那么则执行操作!如果不是就一直循环!(自旋锁)
- CAS是一种系统原语,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
乐观锁是一种思想,CAS只是这种思想的一种实现方式。底层用Unsafe类实现:==Unsafe相当于一个后门 ,基于该类可以直接操作特定内存的数据==
并发包中的原子操作类AtomicInteger来看下,如何在不使用锁的情况下保证线程安全,主要看下getAndIncrement方法,相当于i++的操作:
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next)) //CAS实现
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); //底层用Unsafe类调用本地C++方法操作内存
}
}
解决ABA问题:
线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。
解决办法:
原子引用:AtomicStampedReference / AtomicMarkableReference
(乐观锁)引入原子引用,给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0); public static void main(String[] args){ Thread main = new Thread(() -> { System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference()); int stamp = atomicStampedRef.getStamp(); //获取当前标识别 try { Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行 } catch (InterruptedException e) { e.printStackTrace(); } //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败 boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess); },"主操作线程"); Thread other = new Thread(() -> { Thread.yield(); // 确保thread-main 优先执行 atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference()); atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());},"干扰线程"); main.start(); other.start(); }
* 自己实现一个自旋锁
利用AtomicReference 中的cas操作,实现自旋锁
public class SpinLockDemo{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void Lock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
while(!atomicReference.compareAndSet(null,thread)){ //用这个循环实现自旋,如果是空的,那我们把thread放进去
}
}
public void UnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);//把当前线程踢出去,置为null
System.out.println(Thread.currentThread().getName()+"\t"+"-------task over,unLock.....");
}
public static void main(String[] args){
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.Lock();
try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.UnLock();
},"A").start();
//暂停一会儿线程,保证A线程先于B线程启动并完成
try { TimeUnit.MILLISECONDS.sleep( 500); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.Lock();//B -----come in B只是尝试去抢锁,但是一直在自旋。
spinLockDemo.UnLock();//A结束后 B立马抢到锁,然后马上结束了
},"B").start();
}
}
//A -----come in
//B -----come in
//A -------task over,unLock.....
//B -------task over,unLock.....