0
点赞
收藏
分享

微信扫一扫

【Java】之 CAS


文章目录

  • ​​一、定义​​
  • ​​二、测试并使用​​
  • ​​1. `synchronized`​​
  • ​​2. `cas`​​
  • ​​3. 由此可看出​​
  • ​​三、问题​​
  • ​​(1) 循环时间过长​​
  • ​​(2) 只能保证一个共享变量的原子操作​​
  • ​​(3) ABA 问题​​
  • ​​四、参考资料​​

一、定义

CAS(​​compare and swap​​),是一种多线程无锁的编程方式。

当修改多个线程可能同时操作的属性时,给定​​期望值​​​和​​要更新的值​​​,如果​​当前值​​​与​​期望值​​一致,则更新为要设置的值

CAS的底层是通过将​​读取-比较-设置​​三个操作作为一个指令执行,在执行期间不会有其他线程改变变量。这保证了操作的原子性。

CAS其以乐观的态度进行操作,不断循环等待进行,对比而言,线程切换需要 8万个cup时钟周期,而循环重试只需要 几个cup时钟。

CAS需要三个参数:

  • offset(内存地址)
  • expect(期望值)
  • update(需要更新的值)
    操作成功返回true,否则返回false。



二、测试并使用

1. ​​synchronized​

public class SynchronizedTest {

static int count = 0;

public static void main(String[] args) throws InterruptedException {

final Object lock = new Object();

Thread[] ths = new Thread[10000];
for(int i=0;i<ths.length;i++) {
Thread thread = new Thread(new Runnable() {

public void run() {
for(int i=0;i<10000;i++) {
synchronized (lock) {
count ++;
}
}
}
});
ths[i] = thread;
}
long begin = System.currentTimeMillis();

for(int i = 0; i < ths.length; i++) {

ths[i].start();
ths[i].join();
}
System.out.println(count);// 100000000
System.out.println(System.currentTimeMillis() - begin); //4073
}
}



2. ​​cas​

public class CASTest {

static final AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {

Thread ths [] = new Thread[10000];
for(int i=0;i<ths.length;i++) {
Thread thread = new Thread(new Runnable() {

public void run() {
for(int i=0;i<10000;i++) {
count.incrementAndGet();
}

}
});
ths[i] = thread;
}
long begin = System.currentTimeMillis();

for(int i = 0; i < ths.length; i++) {

ths[i].start();
ths[i].join();
}

System.out.println(count.get());// 100000000
System.out.println(System.currentTimeMillis() - begin); // 1785
}
}



3. 由此可看出

CAS 性能要比 ​​Synchronized​​ 快上 2.8倍




三、问题

(1) 循环时间过长

若一直不成功, 则会一直尝试, 时间过长, 可能会给CPU带来很大的开销



(2) 只能保证一个共享变量的原子操作

只针对单一变量, 有其局限性



(3) ABA 问题

假如链表为: ​​head->A->B->C​​​,线程t1要将​​head->B,cas(A,B)​​​,此过程中线程t2进行了head->A->C->D,此时B已经处于游离状态,B.next=null,切换到线程t1,t1发现header还是A,就进行交换
​​​head->B​​​,这时链表丢失了C和D。
以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference也实现了这个功能。

private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); //初始值为100,初始时间戳为0
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); //cas 还要判断时间戳
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);




四、参考资料

  1. ​​https://zhuanlan.zhihu.com/p/34556594​​
  2. <<码出高效>>


举报

相关推荐

0 条评论