• java多线程之CAS无锁常见API


    1.背景

    这一节,就是学习常用的cas对象与api

    .....

    2.原子整数

    直接看代码吧,或者看API文档

    2.1.AtomicInteger的API演示

    package com.ldp.demo06Atomic;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/16 9:15
     * @description <p>
     * 原子整数
     * JUC 并发包提供了:
     * AtomicBoolean
     * AtomicInteger
     * AtomicLong
     * </p>
     */
    public class Test01 {
        public static void main(String[] args) {
            AtomicInteger i = new AtomicInteger(10);
            // 1.
            System.out.println("i=" + i.get() + ",执行i.getAndIncrement(),结果为:" + i.getAndIncrement() + ",实际i=" + i.get());
            System.out.println("i=" + i.get() + ",执行i.incrementAndGet(),结果为:" + i.incrementAndGet() + ",实际i=" + i.get());
            // 2.
            System.out.println("i=" + i.get() + ",执行i.getAndDecrement(),结果为:" + i.getAndDecrement() + ",实际i=" + i.get());
            System.out.println("i=" + i.get() + ",执行i.decrementAndGet(),结果为:" + i.decrementAndGet() + ",实际i=" + i.get());
            // 3.
            System.out.println("i=" + i.get() + ",执行i.getAndAdd(10),结果为:" + i.getAndAdd(10) + ",实际i=" + i.get());
            System.out.println("i=" + i.get() + ",执行i.addAndGet(10),结果为:" + i.addAndGet(10) + ",实际i=" + i.get());
            //4. P表示i的值
            System.out.println("i=" + i.get() + ",执行i.getAndUpdate(p -> p - 2),结果为:" + i.getAndUpdate(p -> p - 2) + ",实际i=" + i.get());
            System.out.println("i=" + i.get() + ",执行i.updateAndGet(p -> p - 2),结果为:" + i.updateAndGet(p -> p - 2) + ",实际i=" + i.get());
            //5. P表示原来的值i , X表示修改的值10
            System.out.println("i=" + i.get() + ",执行i.getAndAccumulate(10, (p, x) -> p + x),结果为:" + i.getAndAccumulate(10, (p, x) -> p + x) + ",实际i=" + i.get());
            System.out.println("i=" + i.get() + ",执行i.accumulateAndGet(10, (p, x) -> p + x),结果为:" + i.accumulateAndGet(10, (p, x) -> p + x) + ",实际i=" + i.get());
            /**
             * 测试执行结果
             * i=10,执行i.getAndIncrement(),结果为:10,实际i=11
             * i=11,执行i.incrementAndGet(),结果为:12,实际i=12
             * i=12,执行i.getAndDecrement(),结果为:12,实际i=11
             * i=11,执行i.decrementAndGet(),结果为:10,实际i=10
             * i=10,执行i.getAndAdd(10),结果为:10,实际i=20
             * i=20,执行i.addAndGet(10),结果为:30,实际i=30
             * i=30,执行i.getAndUpdate(p -> p - 2),结果为:30,实际i=28
             * i=28,执行i.updateAndGet(p -> p - 2),结果为:26,实际i=26
             * i=26,执行i.getAndAccumulate(10, (p, x) -> p + x),结果为:26,实际i=36
             * i=36,执行i.accumulateAndGet(10, (p, x) -> p + x),结果为:46,实际i=46
             */
        }
    }
    View Code

    3.原子引用

    2.2.AtomicReference的API演示

    package com.ldp.demo06Atomic;
    
    import org.junit.Test;
    
    import java.math.BigDecimal;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/16 9:42
     * @description <p>
     * 原子引用
     * 为什么需要原子引用类型?
     * AtomicReference
     * AtomicMarkableReference
     * AtomicStampedReference
     * <p>
     * Stamp:标记印记
     * </p>
     */
    public class Test02 {
        @Test
        public void test01AtomicReference() {
            AtomicReference<BigDecimal> ref = new AtomicReference<>(new BigDecimal("3.14"));
            while (true) {
                BigDecimal prev = ref.get();
                BigDecimal next = prev.add(new BigDecimal("0.2"));
                boolean b = ref.compareAndSet(prev, next);
                if (b) {
                    System.out.println("修改成功,ref=" + ref.get());
                    break;
                }
            }
        }
    }
    View Code

    2.3.ABA问题与解决

    package com.ldp.demo06Atomic;
    
    import com.common.MyThreadUtil;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 8:14
     * @description
     */
    public class Test03ABA {
        private static AtomicReference<String> ref = new AtomicReference<>("A");
    
        /**
         * ABA问题与解决
         * 代码执行结果:
         * A修改为B-->true
         * B修改为A-->true
         * A修改为C-->true
         *
         * @param args
         */
        public static void main(String[] args) {
            String prev = ref.get();
            new Thread(() -> {
                // A 修改为B
                System.out.println("A修改为B-->" + ref.compareAndSet(ref.get(), "B"));
                // B修改为A
                System.out.println("B修改为A-->" + ref.compareAndSet(ref.get(), "A"));
            }, "ABA").start();
            MyThreadUtil.sleep(1);
            // A修改为C
            System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C"));
        }
    }
    View Code

    2.4.AtomicStampedReference解决ABA问题

    package com.ldp.demo06Atomic;
    
    import com.common.MyThreadUtil;
    
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 8:14
     * @description
     */
    public class Test03ABAStamped {
        private static AtomicStampedReference<String> ref = new AtomicStampedReference<String>("A", 1);
    
        /**
         * ABA问题解决思路
         * <p>
         * 主线程仅能判断出共享变量的值与最初值 A 是否相同,不知到从 A 改为 B 又 改回 A 的情况
         * 如果主线程希望:只要有其它线程【修改了】共享变量,那么自己的 cas 就算失败,
         * 这时,需要比较值和版本号
         * <p>
         * 代码执行结果:
         * A修改为B-->true
         * B修改为A-->true
         * A修改为C-->false
         *
         * @param args
         */
        public static void main(String[] args) {
            String prev = ref.getReference();
            int stamp = ref.getStamp();
            new Thread(() -> {
                // A 修改为B
                System.out.println("A修改为B-->" + ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1));
                // B修改为A
                System.out.println("B修改为A-->" + ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1));
            }, "ABA").start();
            MyThreadUtil.sleep(1);
            // A修改为C
            System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C", stamp, stamp + 1));
        }
    }

    View Code

    2.5.AtomicMarkableReference解决ABA问题

    package com.ldp.demo06Atomic;
    
    import com.common.MyThreadUtil;
    
    import java.util.concurrent.atomic.AtomicMarkableReference;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 8:14
     * @description
     */
    public class Test03Markable {
        private static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", true);
    
        /**
         * ABA问题与解决
         * 代码执行结果:
         * marked-1=true
         * A修改为B-->true
         * marked-2=false
         * B修改为A-->true
         * marked-3=true
         * A修改为C-->true
         * marked-4=false
         * 有时候,并不关心引用变量更改了几次,只是关心是否更改过,所以就有了AtomicMarkableReference
         *
         * @param args
         */
        public static void main(String[] args) {
            String prev = ref.getReference();
            System.out.println("marked-1=" + ref.isMarked());
            new Thread(() -> {
                // A 修改为B
                System.out.println("A修改为B-->" + ref.compareAndSet(ref.getReference(), "B", true, false));
                System.out.println("marked-2=" + ref.isMarked());
                // B修改为A
                System.out.println("B修改为A-->" + ref.compareAndSet(ref.getReference(), "A", false, true));
            }, "ABA").start();
            MyThreadUtil.sleep(1);
            System.out.println("marked-3=" + ref.isMarked());
            // A修改为C
            System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C", true, false));
            System.out.println("marked-4=" + ref.isMarked());
        }
    }
    View Code

    4.原子数组

    package com.ldp.demo06Atomic;
    
    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicIntegerArray;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 8:55
     * @description <p>
     * 原子数组
     * AtomicIntegerArray
     * AtomicLongArray
     * AtomicReferenceArray
     * </p>
     */
    public class Test04Array {
        /**
         * 测试自定义的数组
         * int[10] 存在线程不安全
         */
        @Test
        public void test01() throws InterruptedException {
            myArray(() -> new int[10],
                    (array) -> array.length,
                    (array, index) -> array[index]++,
                    (array) -> System.out.println(Arrays.toString(array)));
        }
    
        /**
         * 测试自定义的数组
         * AtomicIntegerArray 线程安全
         */
        @Test
        public void test02() throws InterruptedException {
            myArray(
                    () -> new AtomicIntegerArray(10),
                    (array) -> array.length(),
                    (array, index) -> array.getAndIncrement(index),
                    array -> System.out.println(array)
            );
        }
    
        /**
         * @param supplierArray  提供一个数组
         * @param functionLength 获取数组长度
         * @param addArray       数组元素自增1
         * @param printArray     打印数组
         * @param <T>            数组的泛型
         */
        public <T> void myArray(Supplier<T> supplierArray,
                                Function<T, Integer> functionLength,
                                BiConsumer<T, Integer> addArray,
                                Consumer<T> printArray
        ) throws InterruptedException {
            // 定义一个放线程的集合
            List<Thread> list = new ArrayList<>();
            // 1.提供一个数组
            T array = supplierArray.get();
            // 2.获取数组长度的方法
            Integer length = functionLength.apply(array);
            // 遍历数组,使用1000个线程,每个线程给每个元素加1
            for (int i = 0; i < length; i++) {
                // 1000个线程,每个线程给每个元素加1
                Thread t = new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        // 3.自增方法,传入array与下标index
                        addArray.accept(array, j % length);
                    }
                });
                list.add(t);
            }
            // 启动所有线程
            for (Thread thread : list) {
                thread.start();
            }
            // 等待所有线程执行完
            for (Thread thread : list) {
                thread.join();
            }
            // 4.打印数组的方法
            printArray.accept(array);
        }
    }
    View Code

    5.字段更新器

    package com.ldp.demo06Atomic;
    
    import org.junit.Test;
    
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 9:56
     * @description <p>
     * 字段更新器
     * AtomicReferenceFieldUpdater
     * AtomicIntegerFieldUpdater
     * AtomicLongFieldUpdater
     * </p>
     */
    public class Test05Field {
        private volatile int age = 18;
    
        @Test
        public void test01() {
            AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Test05Field.class, "age");
            // 创建Test05Field对象
            Test05Field test05Field = new Test05Field();
            // 将18修改为 30岁
            fieldUpdater.compareAndSet(test05Field, test05Field.age, 30);
            System.out.println("当前年龄:" + test05Field.age);
        }
    }
    View Code

    6.原子累加器:LongAdder

    package com.ldp.demo06Atomic;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicLong;
    import java.util.concurrent.atomic.LongAdder;
    import java.util.function.Consumer;
    import java.util.function.Supplier;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/18 10:13
     * @description
     */
    @Slf4j
    public class Test06LongAdder {
        /**
         * AtomicLong 与 LongAdder 测试
         * LongAdder:在多线程并发的情况下,LongAdder会明显优于AtomicLong
         * <p>
         * LongAdder的原理:
         * 在由线程竞争cpu时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]...
         * 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
         * <p>
         * 09:35:18.967 [main] -> start.....
         * num=10000000,耗时:172 毫秒
         * num=10000000,耗时:203 毫秒
         * num=10000000,耗时:187 毫秒
         * num=10000000,耗时:188 毫秒
         * num=10000000,耗时:171 毫秒
         * 09:35:20.013 [main] -> ------------------------------
         * num=10000000,耗时:157 毫秒
         * num=10000000,耗时:93 毫秒
         * num=10000000,耗时:63 毫秒
         * num=10000000,耗时:78 毫秒
         * num=10000000,耗时:78 毫秒
         * 09:35:20.482 [main] -> end.....
         *
         * @throws InterruptedException
         */
        @Test
        public void test01() throws InterruptedException {
            log.info("start.....");
            for (int i = 0; i < 5; i++) {
                methodAdd(() -> new AtomicLong(),
                        (num) -> num.getAndIncrement());
            }
            log.info("------------------------------");
            for (int i = 0; i < 5; i++) {
                methodAdd(() -> new LongAdder(),
                        (num) -> num.increment());
            }
            log.info("end.....");
        }
    
        /**
         * 给一个数组累加
         *
         * @param supplierNum 传入的数字
         * @param addNum      数字加一
         * @param <T>
         */
        public <T> void methodAdd(Supplier<T> supplierNum, Consumer<T> addNum) throws InterruptedException {
            long start = System.currentTimeMillis();
            List<Thread> list = new ArrayList<>();
            T num = supplierNum.get();
            // 3个线程
            for (int i = 0; i < 100; i++) {
                Thread thread = new Thread(() -> {
                    // 每个线程加 10万次
                    for (int j = 0; j < 100000; j++) {
                        addNum.accept(num);
                    }
                });
                list.add(thread);
            }
            // 启动线程
            for (Thread thread : list) {
                thread.start();
            }
            // 等待线程结束
            for (Thread thread : list) {
                thread.join();
            }
            long end = System.currentTimeMillis();
            System.out.println("num=" + num + ",耗时:" + ((end - start)) + " 毫秒");
        }
    }
    View Code

    7.CAS实现自定义锁

    package com.ldp.demo06Atomic;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 02/19 9:42
     * @description <p>
     * 利用cas的特性实现自己的锁
     * </p>
     */
    public class Test07MyCasLock {
        AtomicInteger lock = new AtomicInteger(0);
    
        /**
         * 获取锁
         */
        public void lock() {
            while (true) {
                if (lock.compareAndSet(0, 1)) {
                    // 取锁成功
                    break;
                }
            }
        }
    
        /**
         * 释放锁
         */
        public void unlock() {
            lock.set(0);
        }
    
        /**
         * 测试自定义的锁
         * 注意:自定义的锁只是练习对cas的理解,不作为生产的实现方式
         *
         * @param args
         */
        public static void main(String[] args) {
           // UnsafeAccessor.
            Test07MyCasLock casLock = new Test07MyCasLock();
            casLock.lock();
            try {
                System.out.println("加锁成功....");
            } finally {
                casLock.unlock();
                System.out.println("锁已经释放...");
            }
        }
    }
    View Code

    完美!

  • 相关阅读:
    [BJWC2010]外星联络
    [NOI2015]品酒大会
    工艺 /【模板】最小表示法
    [NOI2016]优秀的拆分
    [HEOI2016/TJOI2016]字符串
    [SDOI2016]生成魔咒
    【模板】后缀自动机 (SAM)【SA解法】
    [湖南集训]图森
    [USACO17DEC]Standing Out from the Herd P
    Annihilate
  • 原文地址:https://www.cnblogs.com/newAndHui/p/15912150.html
Copyright © 2020-2023  润新知