• 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();
            }
    }
  • 相关阅读:
    忍道
    2020.12.27
    2020.12.26
    2020.12.25
    记录
    卸载抖音
    汉化报告修改配置文件
    tcp校验client客户端的合法性
    tcp连接发送命令客户端接收后返回结果给服务端
    logging模块
  • 原文地址:https://www.cnblogs.com/killbug/p/3969268.html
Copyright © 2020-2023  润新知