首页 > 学院 > 开发设计 > 正文

java CAS

2019-11-14 21:08:23
字体:
来源:转载
供稿:网友
java CAS在Doug Lea提供的cucurenct包(J.U.C)中,CAS理论是实现整个java包的基石。Compare and Swap

在这里,CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”(这段描述引自《Java Concurrency in PRactice》)

Compare and Set

在理解了什么是 Compare and Swap 之后,理解 Compare and Set 便很容易了。Compare and Set 就是利用 Compare and Swap 实现的非阻塞的线程安全的写操作算法。它的实现过程如下:首先读取你要更改的数据的原值,然后将其和你要更新成的值作为 Compare and Swap 操作的两个参数,如果 Compare and Swap 的返回值和原值不同,便重复这一过程,直至成功。写成伪代码如下

int old;int new;do {    old = value.get();    new = doSomeCalcBasedOn(old)while (value.compareAndSwap(old, new));

Compare and Set 是一个非阻塞的算法,这是它的优势。但是有一个问题就是存在这样的可能,在每次读操作之后,写操作之前,都有另外一个线程更改了原先的值,这样 Compare and Set 操作就会不停地重试。但这样的可能只存在于理论,在实际中很少发生。

Compare and Set 广泛使用在 Java 5 中的 Atomic 类中,其它的诸如 ReetrantLock、Semaphore 等的类也通过 AbstractQueuedSynchronizer 类间接地使用了 Compare and Set。AtomicInteger 的方法中i++的实现:
    public final int getAndIncrement() {        for (;;) {            int current = get();//先取出当前时间点的值            int next = current + 1;            if (compareAndSet(current, next))//然后尝试赋新值,在赋值时判断这个值是否改变。                return current;        }    }    public final boolean compareAndSet (int expect, int update) {     return unsafe.compareAndSwapInt( this, valueOffset, expect, update);    }

ABA问题:CAS操作容易导致ABA问题,也就是在做a++之间,a可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为a的值没有变。在运用CAS做Lock-Free操作中有一个经典的ABA问题:线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,

例如下面的例子:

现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:

head.compareAndSet(A,B);

在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:

此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:

其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题

例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败:

import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger ;import java.util.concurrent.atomic.AtomicStampedReference;public class ABA {        private static AtomicInteger atomicInt = new AtomicInteger(100);        private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);        public static void main(String[] args) throws InterruptedException {                Thread intT1 = new Thread( new Runnable() {                        @Override                        public void run() {                                atomicInt.compareAndSet(100, 101);                                atomicInt.compareAndSet(101, 100);                        }                });                Thread intT2 = new Thread( new Runnable() {                        @Override                        public void run() {                                try {                                        TimeUnit.SECONDS.sleep(1);                                } catch (InterruptedException e) {                                }                                boolean c3 = atomicInt.compareAndSet(100, 101);                                System. out.println(c3); // true                        }                });                intT1.start();                intT2.start();                intT1.join(); //join方法保证intT1执行完毕后执行intT2                intT2.join();                Thread refT1 = new Thread( new Runnable() {                        @Override                        public void run() {                                try {                                        TimeUnit.SECONDS.sleep(1);                                } catch (InterruptedException e) {                                }                                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);                                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);                        }                });                Thread refT2 = new Thread( new Runnable() {                        @Override                        public void run() {                                int stamp = atomicStampedRef.getStamp();                                try {                                        TimeUnit.SECONDS.sleep(2);                                } catch (InterruptedException e) {                                }                                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);                                System. out.println(c3); // false                        }                });                refT1.start();                refT2.start();                refT1.join();                refT2.join();        }}


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表