• CAS概念和解析


    一、CAS概念  

    package syncbasics;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     * 多线程访问同一份数据,会产生竞争,race condition => 竞争条件
     * 就有可能产生数据的不一致,并发访问之下产生的不期望出现的结果
     * 如何保障数据的一致呢?---->线程同步(线程执行的顺序安排好),
     * 具体:保障操作的原子性(Atomicity)
     * 1.悲观的认为这个操作会被别的线程打断(悲观锁)synchronized
     * 2.乐观的认为这个操作不会被别的线程打断(乐观锁)cas操作
     * CAS = Compare And Set/Swap/Exchange
     *
     * ++操作:
     * 把n从内存里面读到寄存器里,加完了之后,再写回去。
     * 还没来得及写回去的时候,另外的线程读到了原值,
     * 因此,++这个正在执行的操作被另外线程打断了。
     *
     * 只要我们能保证这个执行的操作不被打断,即保证我这个线程读过来之后改完这个值1,再写回去之后,其他线程才能执行,
     * 那最后的结果一定是对的。这种不能够被打断的操作称之为原子操作。
     *
     * 什么样的语句是原子性的?什么样的不是?
     * java内存中的8大原子操作,了解即可。要查汇编手册。
     *
     * synchronized:
     * 让原来的并发变成了序列化。
     * synchronized本身是保证可见性的,n++结束了之后这个线程一定是要和主内存做同步,主内存里一定都是最新的。
     * synchronized保障了可见性、原子性。
     * 那么保证有序性吗?
     * ----不可以,synchronized里面的代码块里面的操作指令完全有可能换顺序。DCL要加volatile就是证明了。
     *
     * CAS概念:
     * 还以下面这个小程序n++操作来说明CAS的概念。
     * n开始等于0,线程1把n读过来加1,原来是需要加锁的,现在整个过程不上锁了,把0读过来改成1之后,再往回写
     * 的过程之中做个判断,判断原值依然是否为0,如果依然为0,说明在线程1读0加1的过程中,没有人来过,那就直接把1
     * 写回去,搞定。
     * 万一中间有人改了呢?万一其他线程已经将0改成8了,线程1把1往回写的时候发现原值已经变为8了,不是你所期望的0,
     * 这时候怎么办?那就再来一遍,把8读出来加1变成9,把9往回写的过程之中看看判断原值是否依然为8,如依然为8,说明在我
     * 将8改为9的过程之中没有其他人来过,那就直接将9写回去了,搞定。
     * 当然,如果将8读来加1的过程之中又有人打断了,有人将8改成100了,怎么办?那就把100读过来加1,将101往回写的过程中判断
     * 原值是否依然是100....一直到某一次成功了为止。
     * 你会发现它就在这里不停的循环,读取当前值,计算结果,比较当前值和新值,如果当前值和新值相等,更新为新值。如果不相等,
     * 就再来一遍,总有一次能成功。
     *
     * CAS的ABA问题:
     * 上面的这段话描述里面有个很重要的问题,此0非彼0的问题,线程1把0读过来改成了1,往回写的过程中发现原值依然为0,但是
     * 这个0是不是你所看到的那个0呢?未必,有可能在这个过程之中,这个0被别的线程改成了8,又被别的线程改回了0!中间有个
     * 0->8->0的过程,此0非彼0,A->B->A,这就是ABA问题。
     * 但是咱们这个程序是不存在这个问题,只是理论上有,简单数据类型就算ABA问题,但是对我而言没关系,这种可以不在乎,略过。
     * 但是在有些情况下,是要解决的。如果这个值是一个引用的话,读过来引用值,对它的属性进行了一些修改,它是一个对象,当你再
     * 往回写的时候,有可能这个引用指向的对象里面的内容发生了改变,引用依然还是这个引用,但是里面的内容发生了改变,这时就要
     * 在乎这个ABA问题了。
     *
     * 解决ABA问题:
     * 加version,你的女朋友,你早上离开了她,晚上回来发现依然是她,但此她还是彼她嘛?你比较怀疑,怎么办?在她脑门上写1.0,你就
     * 走了,回来的时候依然是她,不过如果中间经过任何其他线程操作,这个ver1.0都会加1。这时候你发现她脑门上是99.0,那肯定中间经过
     * 了改变,当然就看你在乎不在乎。所以加version,加版本就可以解决。
     *
     * CAS的底层原子性保障:
     * 除了ABA问题,CAS还有一个巨大问题。分析一下:
     * 线程1将0读过来改成1,把1往回写的过程中是一个CAS操作。CAS叫compare and swap,compare and set,伪代码就是:
     * if(v==0){v=1},这个操作实际上底层就是两步:比较和设定。那万一当你执行完if(v==0)后,这个时间点上被另外线程打断,
     * 另外线程把0改成8了,那线程1又将8->1,那还是出问题了,数据还是不一致!
     * 所以,如果想让CAS产生作用的话,必须保证CAS操作本身必须是原子性的。
     *
     * 悲观锁和乐观锁的效率:
     * 不要认为悲观锁的效率一定比乐观锁的效率高,CAS一定比上来就上锁的效率高,不是的,不一定。
     * 悲观锁一般采用什么样的实现呢,一把锁lock,和这把锁关联的队列,这个队列用来等待着这把锁,比如说小明wc蹲着,这把
     * 锁是他的,队列里还有小红、小花、小光、小刚...他们在等着。在队列里排着队,操作系统OS说小红轮到你了,你出来,可以抢
     * 这把锁了,凡是在队列里等待的这些线程,是不消耗CPU资源的。可是与此形成鲜明对比的是,如果这时候小明在这我们用的是乐观锁,
     * 线程会while循环不断看小明有没有出来,很多人,小红、小花...这些人不会坐在那里安安静静的等待,他们会领着裤子原地打转。
     * 这些线程消耗cpu,都是活着的线程,cpu一个是一直要运行他们的while循环,一个是要进行他们的线程切换。而等待队列里的cpu是不占用
     * cpu的,他们状态是parking、waiting或blocked,什么时候OS说轮到你了你再占用cpu资源。所以乐观锁是要消耗cpu的,消耗的比悲观锁要
     * 多一些。
     * 使用场景:
     * 悲观锁:临界区执行时间比较长,等的人很多
     * 乐观锁:时间短,等的人少
     * 还是没有量化啊,实际中该使用悲观锁还是乐观锁呢?压测,可以写两种比较一下时间,实际中决定。
     * 但是实战中啊,就用synchronized,因为synchronized现在做了一系列的优化,它内部既有自旋锁,又有偏向锁,又有重量级锁进行
     * 自适应的升级过程,自动完成锁升级,它的效率已经调试的很不错了。
     *
     */
    public class T00_IPlusPlus {
        private static long n = 0L;
    
        public static void main(String[] args) throws InterruptedException {
            Thread[] threads = new Thread[100];
            CountDownLatch latch = new CountDownLatch(threads.length);
    
            for(int i=0; i<threads.length; i++){
                threads[i] = new Thread(() -> {
                   for(int j=0; j<10000; j++){
                       synchronized (T00_IPlusPlus.class) {
                           n++;
                       }
                   }
                   latch.countDown();
                });
            }
    
            for(Thread t : threads){
                t.start();
            }
    
            latch.await();
    
            System.out.println(n);
        }
    }

    二、AtomicXXX类:

    package atomicxxx;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 这里就是一种CAS机制
     * incrementAndGet每次往回写的时候都要比较一下,看下是否原值是我期望的那个值。
     * incrementAndGet操作自带原子性:
     * unsafe.getAndAddInt->this.compareAndSwapInt->native boolean compareAndSwapInt
     * 得益于CPU在汇编级别上支持指令:cmpxchg,但是cmpxchg不是原子性的,最终实现:
     * lock cmpxchg
     * 所以你会发现,CAS在宏观上我们叫做自旋锁,乐观锁,但它在底层上的实现,微观上的实现实际上是一个悲观锁。
     */
    public class T01_AtomicInteger {
    
        AtomicInteger count = new AtomicInteger(0);
    
        void m(){
            for(int i=0; i<10000; i++){
                count.incrementAndGet();
            }
        }
    
        public static void main(String[] args) {
            T01_AtomicInteger t = new T01_AtomicInteger();
    
            List<Thread> threads = new ArrayList<>();
    
            for(int i=0; i<100; i++){
                threads.add(new Thread(t::m, "thread-" + i));
            }
    
            threads.forEach(o -> o.start());
            threads.forEach(o -> {
                try {
                    o.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            System.out.println(t.count);
        }
    }

    ---

  • 相关阅读:
    Python合集之文件操作(二)
    Python合集之文件操作(一)
    Python合集之异常(二)
    Python合集之异常(一)
    Python合集之模块(五)
    Visual Studio 配置额外工具 Windows Terminal 等
    CMakeList.txt
    alpha智能图像(全栈的进阶之路)
    位运算实现多状态控制
    缓存函数 memorize
  • 原文地址:https://www.cnblogs.com/tenWood/p/16026043.html
Copyright © 2020-2023  润新知