JDK 1.8 atomic 包

只谈谈,不全覆盖

CAS

悲观锁,假设执行当前区域代码都会产生冲突,为了解决冲突,线程A获取锁时,其它线程会被阻塞处于停顿状态,直到锁释放(可能会造成死锁)。CAS概念属于一种乐观锁的策略(或者称为无锁),假设执行当前区域代码都不会发生冲突,不会发生冲突就自然没有线程阻塞,也不会产生死锁问题。如果出现了冲突,CAS(compare and swap) 比较和替换就会不断的去重试,直到当前操作没有冲突。

CAS 的 ABA 问题

  1. 线程1从内存M位置取出A
  2. 线程2从内存M位置取出A
  3. 线程2做了预期值比较,将A替换为B并放到M位置
  4. 线程2从内存M位置取出B
  5. 线程2做了预期值比较,将B替换为A并放到M位置
  6. 线程1做了预期值比较,将A替换为C并放到M位置

此时线程1影响了线程2的状态,也就发生了ABA的问题。所以为了解决乐观锁并发时造成的ABA问题,都会使用AtomicStampedReference 类或者AtomicMarkableReference 类

volatile

volatile从主内存中加载到线程工作内存中的值是最新的。也就是说它解决的是多线程并发变量读时的可见性问题,但无法保证访问变量的原子性。而且volatile只能修饰变量。

原子基本类型

  • AtomicBoolean 保证布尔值的原子性
  • AtomicInteger 保证整型的原子性
  • AtomicLong 保证长整型的原子性

原子数组

  • AtomicIntegerArray 保证整型数组的原子性
  • AtomicLongArray 保证长整型数组原子性

原子字段

  • AtomicIntegerFieldUpdater 保证整型的字段更新
  • AtomicLongFieldUpdater 保证长整型的字段更新

使用原子字段类时,所声明的字段类型必须为volatile

使用方法如下:

    private int sum = 100;
    private volatile int sum1 = 100;
    // 当 sum 或 sum1 为100 时只允许有一个线程进入
    private void atomic4() {
        AtomicIntegerFieldUpdater<T> tAtomicIntegerFieldUpdater =  AtomicIntegerFieldUpdater.newUpdater(T.class, "sum1");
        T t = new T();
        for (int i = 0; i < 10; i++) {
            singleThreadPool.execute(() -> {
                if (sum == 100) {
                    System.out.println(Thread.currentThread().getName() + " :" + "已修改");
                }
            });
        }

        System.out.println("=====");

        for (int i = 0; i < 10; i++) {
            singleThreadPool.execute(() -> {
                if(tAtomicIntegerFieldUpdater.compareAndSet(t, 100, 120)){
                    System.out.print(Thread.currentThread().getName() + " :" + "tAtomicIntegerFieldUpdater 已修改");
                }
            });
        }
        singleThreadPool.shutdown();
    }

原子引用类型

  • AtomicReferenceArray 保证引用数组的原子性
  • AtomicReferenceFieldUpdater 保证引用类型的字段更新
  • AtomicStampedReference 可以解决CASABA问题,类似提供版本号
  • AtomicMarkableReference 可以解决CASABA问题,提供是或否进行判断

原子累加器 JDK 1.8 新增

原有的 Atomic系列类通过CAS来保证并发时操作的原子性,但是高并发也就意味着CAS的失败次数会增多,失败次数的增多会引起更多线程的重试,最后导致AtomicLong的效率降低。新的四个类通过减少并发,将单一value的更新压力分担到多个value中去,降低单个value的“热度”以提高高并发情况下的吞吐量

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

实例应用

  • 只贴了代码片段。验证累加器、整型、数组的原子性
package concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 张博
 */
public class ThreadFactoryBuilder implements ThreadFactory {

    private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    ThreadFactoryBuilder() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY) {
            t.setPriority(Thread.NORM_PRIORITY);
        }
        return t;
    }
}

private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder();
    private static ExecutorService singleThreadPool = new ThreadPoolExecutor(1000, 5000,
            10, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    // 阻塞主线程,循环5000次后通知主线程关闭
    private CountDownLatch begin = new CountDownLatch(5000);
    // 模拟并发量一次200
    private Semaphore semaphore = new Semaphore(200);
    private static int count = 0;
    private void atomic7() {
        DoubleBinaryOperator doubleBinaryOperator = (x, y) -> x + y;
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator(doubleBinaryOperator, count);
        AtomicInteger atomicInteger = new AtomicInteger(0);
        int[] ints = new int[5000];
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5000);
        for (int i = 0; i < 5000; i++) {
            singleThreadPool.execute(() -> {
                try {
                    // 允许200个线程进入,模拟提供稳定并发量
                    semaphore.acquire();
                    count();
                    atomicCount(doubleAccumulator);
                    atomicIntegerCount(atomicInteger);
                    array(ints);
                    atomicArray(atomicIntegerArray);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 每执行一次减1
                begin.countDown();
            });
        }
        try {
            // 没到0一直等待,直到模拟并发结束
            begin.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        singleThreadPool.shutdown();
        System.out.println(count);
        System.out.println(doubleAccumulator.get());
        System.out.println(atomicInteger.get());
        System.out.println(Arrays.toString(ints));
        System.out.println("=========================================================================================================");
        System.out.println(atomicIntegerArray.toString());
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            if ((atomicIntegerArray.get(i) - 5000) != 0) {
                System.out.println(atomicIntegerArray.get(i));
            }
        }
    }

    /**
     * 时间:2018/8/3 上午11:48
     * @apiNote 线程不安全的累加
     */
    private void count() {
        count++;
    }

    /**
     * 时间:2018/8/3 上午11:48
     * @apiNote 原子累加
     */
    private void atomicCount(DoubleAccumulator doubleAccumulator) {
        doubleAccumulator.accumulate(1);
    }

    /**
     * 时间:2018/8/3 上午11:48
     * @apiNote 原子的i++
     */
    private void atomicIntegerCount(AtomicInteger atomicInteger) {
        atomicInteger.incrementAndGet();
    }

    /**
     * 时间:2018/8/3 上午11:48
     * @apiNote 线程不安全的数组操作
     */
    private void array(int[] ints) {
        for(int k = 0; k < 5000; k++) {
            ints[k] += 1;
        }
    }

    /**
     * 时间:2018/8/3 上午11:48
     * @apiNote 原子的数组操作
     */
    private void atomicArray(AtomicIntegerArray atomicIntegerArray) {
        for(int k = 0; k < 5000; k++) {
            atomicIntegerArray.addAndGet(k, 1);
        }
    }

使用AtomicStampedReference解决CASABA问题

    /**
     * 时间:2018/8/3 上午11:58
     * @apiNote 模拟并发导致 CAS 的 ABA 问题
     */
    private void aba() {
        // 原子引用类型
        AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");
        // 原子时间戳引用
        AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A", 0);

        // 线程1
        singleThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "B"));
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("B", "A"));
            System.out.println("=====");
        });

        // 线程2
        singleThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "C"));
            System.out.println("=====");
        });

        // 线程3
        singleThreadPool.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "B", stampedReference.getStamp(), stampedReference.getStamp() + 1));
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("B", "A", stampedReference.getStamp(), stampedReference.getStamp() + 1));
            System.out.println("=====");
        });

        // 线程4
        singleThreadPool.execute(() -> {
            // 模拟并发时与线程3同时得到内存中的A的时间戳
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " : 线程4 处理 cas 之前" + stamp);
            // 线程4休眠2秒,模拟让线程3已经操作完成 A -> B -> A 的 CAS
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 得到线程3操作完的时间戳
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.getStamp());
            // 线程4进行 A -> C 的 CAS 操作。这时会失败。解决 ABA 问题
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "C", stamp, stamp + 1));
        });

        singleThreadPool.shutdown();
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,681评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • 关于哲同学使用手机的问题上经常很纠结,和哲也常发生冲突。对于玩手机的事我们和哲一直都不能很好的达成共识。 在商定孩...
    胡胡_9e10阅读 140评论 0 0