多线程之CAS

不同于synchronized这种悲观的阻塞同步机制,java.util.concurrent包中还借助CAS实现了乐观的非阻塞同步机制

CAS(CompareAndSwap):比较并交换

CAS有包含三个操作数:内存值V旧的预期值A以及更新值B当且仅当V的值等于A的时候,CAS才通过原子方式用更新值B来更新V的值。否则不做任何操作。

CAS的含义是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”。


以两个线程都执行i++为例,看看CAS的作用:

我们都知道volatile能保证变量的可见性,但不能保证变量操作的原子性。因此我们就算volatile int i = 0,两个线程都进行i++操作,也不能保证i最后的值是2。

i++这个操作其实包括了读-改-写三个操作,因此可能两个线程都读取了i = 0,最终写回到主存的i就是1而不是2。

而如果用上CAS的话,就可以避免这个问题(下面用三个操作数V,A,B进行说明),

比如线程1读到i的内存值V1 = 0,线程2同样读到V2= 0.然后线程1读取i的预期值A1=0,并计算出更新值B1 =  1进行尝试更新。此时V1 == A1 ,所以V1 = B1,将i的内存值更新成1;然后线程2读取i的预期值A2 = 1。此时V2 != A2,所以不进行任何操作。并再次循环重新读取V,A,B。这就是CAS自旋

原子变量类的AtomicInteger的getAndIncrement方法(相当于i++)就是基于这样的CAS自旋。

/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    for (;;) {
        int current = get();               //预期值
        int next = current + 1;         //更新值
        if (compareAndSet(current, next))
            return current;
    }
}

compareAndSet方法:

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
unsafe.compareAndSwapint是一个native方法。所以再用一段代码来模拟下CAS下的i++。

package com.bckj.Thread;

/**
 * 模拟CAS算法
 */
public class TestCompareAndSwap {
    public static void main(String[] args) throws InterruptedException {
        final SimulatedCAS cas = new SimulatedCAS();
        for(int i = 0 ; i< 10;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(;;) {
                        int expectedValue = cas.get();
                        int newValue = expectedValue+1;
                        System.out.print("");  //增加线程被抢占的几率
              if (cas.compareAndSet(expectedValue, newValue)) {
                            System.out.println(Thread.currentThread().getName()+"执行value++成功!");
                            break;
                        } else
                            System.out.println(Thread.currentThread().getName()+"执行value++的时候,有其他程序修改过valueCAS自旋重新读取内存值");
                    }
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println("最终结果:"+cas.get());
    }
}
class SimulatedCAS{
    private volatile int value;
    public synchronized int get(){
        return value;
    }
    public synchronized int compareAndSwap(int expectedValue,int newValue){
        int oldValue = this.value;
        if(oldValue == expectedValue){
            this.value = newValue;
        }
        return oldValue;
    }
    public synchronized boolean compareAndSet(int expectedValue,int newValue){
        return expectedValue == compareAndSwap(expectedValue,newValue);
    }
}
执行结果:



java.util.concurrent包中除了原子变量类,还有AQS(AbstractQueuedSynchronizer)等也使用了CAS。

比如AQS的enq方法等。

private Node enq(final Node node) {
    for (;;) {		//CAS"自旋",直到成功加入队尾
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}



可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。(ABA问题大致是指内存值V从A变成B,再从B变成A,这时候CAS会认为没有发生变化。解决ABA问题可以考虑增加一个修改计数,只有修改计数不变的且V值不变的情况下才做操作,也可以考虑引入版本号,当版本号相同时才做操作等)



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值