浅谈JUC原子类

概念

并发编程的场景下,当多个线程同时操作一个共享变量时候可能会出现线程安全的问题,那么这个时候这么解决呢?

下面我们举例说明:

当程序更新一个变量时,如果在多线程同时更新这个变量,可能会得到期望之外的值,而这个问题常用的方法是使用synchronized进行控制达到线程安全的目的。但是由于synchronized采用的是悲观锁策略,并不是一种高效的解决方案。

而在Java1.5开始提供java.util.concurrent.atomic包下提供一系列操作简单,性能高效,并能保证线程安全。atomic包下的这些类都是采用的乐观锁机制去原子更新数据,底层实现是使用CAS 原理实现。

CAS 原理

理解atomic包下这些原子操作类的实现原理,首先我们清楚什么是CAS操作。那到底什么是CAS呢?

我们知道synchronized使用锁时,线程获取锁是使用的是悲观锁,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。

而CAS操作(无锁操作)使用是乐观锁,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止(自旋锁)。

CAS的操作过程: CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值N赋值给V。反之,V和O不相同,表明该值已经被其他线程改过了则该旧值O不是最新版本的值了,所以不能将新值N赋给V,返回V即可。当多个线程使用CAS操作一个变量是,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。CAS 的实现需要硬件层面的支撑。

「synchronized 和 CAS 的比较」

synchronized存在的问题:线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步即阻塞同步。CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。

原子类分为几种类型:基本类型原子类,数组类型原子类。引用类型原子类,对象属性修改原子类,原子操作增强类。下面我们分类介绍:

基本类型原子类

基本类型原子类,主要有三个 AtomicInteger、AtomicBoolean、AtomicLong

public class AutomicIntegerTest {
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void addPlus() {
        atomicInteger.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        AutomicIntegerTest atomic = new AutomicIntegerTest();
        // 10个线程进行循环100次调用addPlusPlus的操作,最终结果是10*100=1000
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100; j++) {
                        atomic.addPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "t" + "获取到的result:" + atomic.atomicInteger.get());
    }
}

main 获取到的result:1000    

AtomicBoolean可以作为中断标识停止线程的方式

public class AtomicBooleanDemo {

    public static void main(String[] args) {
        AtomicBoolean atomicBoolean=new AtomicBoolean(false);

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"t"+"coming.....");
            while(!atomicBoolean.get()){
                System.out.println("自旋!!!!!!");
            }
            System.out.println(Thread.currentThread().getName()+"t"+"over.....");
        },"A").start();

        new Thread(()->{
            atomicBoolean.set(true);
        },"B").start();
    }

}

AtomicInteger的底层是CAS+自旋锁的思想,适用于低并发的全局计算,高并发后性能急剧下降,主要是由于N个线程CAS操作修改线程的值,每次只有一个成功过,其他N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子CPU 就会很高。所以高并发下推荐使用LongAdder。

数组类型原子类

数组类型原子类,主要有三个AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,不做介绍类似于上面。

引用类型原子类

引用类型原子类,主要有三个AtomicReference 、AtomicStampedReference 、AtomicMarkableReferenc。使用AtomicReference来实现自旋锁案例

public class AtomicReferenceThreadDemo {

    static AtomicReference<Thread> atomicReference = new AtomicReference<>();
    static Thread thread;
    public static void lock(){
        thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"t"+"coming.....");
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }
    public static void unlock(){
        System.out.println(Thread.currentThread().getName()+"t"+"over.....");
        atomicReference.compareAndSet(thread,null);
    }

    public static void main(String[] args) {
        new Thread(()->{
            AtomicReferenceThreadDemo.lock();
            try { TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {e.printStackTrace();}
            AtomicReferenceThreadDemo.unlock();
        },"A").start();

        new Thread(()->{
            AtomicReferenceThreadDemo.lock();
            AtomicReferenceThreadDemo.unlock();
        },"B").start();
    }
}

但是使用这个原子引用可能会存在ABA问题,那么如何解决呢?那我们使用AtomicStampedReference 解决ABA问题

public class AtomicStampedReferenceDemo {

    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1001);

    public static void main(String[] args) {

        // 产生ABA
        new Thread(() -> {
            atomicReference.compareAndSet(100101);
            atomicReference.compareAndSet(101100);
        }, "t1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(1002019) + "t" + atomicReference.get());
        }, "t2").start();

        System.out.println("使用AtomicStampedReference 解决ABA 问题");

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "t 第1次版本号" + stamp + "t值是" + stampedReference.getReference());
            //暂停1秒钟t3线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            stampedReference.compareAndSet(100101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "t 第2次版本号" + stampedReference.getStamp() + "t值是" + stampedReference.getReference());
            stampedReference.compareAndSet(101100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "t 第3次版本号" + stampedReference.getStamp() + "t值是" + stampedReference.getReference());
        }, "t3").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "t 第1次版本号" + stamp + "t值是" + stampedReference.getReference());
            //保证线程3完成存在BA
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = stampedReference.compareAndSet(1002019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "t 修改成功否" + result + "t最新版本号" + stampedReference.getStamp());
            System.out.println("最新的值t" + stampedReference.getReference());
        }, "t4").start();
    }
}


AtomicStampedReference修改带有版本号,会记录每一次修改的版本号,当多个线程同时访问的时候,除了判断内存中的值和预期值是否相同外还需要判断版本号是否一致,可以解决ABA问题。

t3  第1次版本号1 值是100
t4  第1次版本号1 值是100
true 2019
t3  第2次版本号2 值是101
t3  第3次版本号3 值是100
t4  修改成功否false 最新版本号3
最新的值 100

AtomicMarkableReferenc  解决是否修改过,类似一次性筷子(只判断有没有修改过)

对象属性修改原子类

对象属性修改原子类,主要有三个AtomicIntegerFieldUpdater、 AtomicLongFieldUpdater 、AtomicReferenceFieldUpdater。以一种线程安全的方式操作非线程安全对象内的某些字段,更新的对象属性必须使用public volatile修饰符 AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值

public class AtomicIntegerFieldUpdaterDemo {

    private static CountDownLatch countDownLatch = new CountDownLatch(1000);
    Score score = new Score();

    public static void main(String[] args) throws InterruptedException {
        Score score = new Score();
        for (int j = 0; j < 1000; j++) {
            new Thread(() -> {
                score.addTotalScore(score);
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println("totalScore的值:" + score.totalScore);
    }
}

class Score {
    public volatile int totalScore = 0;
    private static AtomicIntegerFieldUpdater atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Score.class, "totalScore");

    public void addTotalScore(Score score) {
        atomicIntegerFieldUpdater.incrementAndGet(score);
    }
}

AtomicReferenceFieldUpdater:原子更新引用类型字段的值

public class AtomicReferenceFieldUpdaterDemo {

    public static void main(String[] args) {
        Tests myCar=new Tests();
        AtomicReferenceFieldUpdater<Tests,Boolean> atomicReferenceFieldUpdater=
                AtomicReferenceFieldUpdater.newUpdater(Tests.class,Boolean.class,"flag");
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                if(atomicReferenceFieldUpdater.compareAndSet(myCar,Boolean.FALSE,Boolean.TRUE)){
                    System.out.println(Thread.currentThread().getName()+"t"+"---init.....");
                    try { TimeUnit.SECONDS.sleep(2);  } catch (InterruptedException e) {e.printStackTrace();}
                    System.out.println(Thread.currentThread().getName()+"t"+"---init.....over");
                }else{
                    System.out.println(Thread.currentThread().getName()+"t"+"------其它线程正在初始化");
                }
            },String.valueOf(i)).start();
        }

    }
}

class Tests{
    public volatile Boolean flag=false;
}

原子操作增强类

原子操作增强类,主要有三个 LongAdder 、 LongAccumulator、 DoubleAdder、  DoubleAccumulator LongAdder只能用来计算加法、减法,且从零开始计算 LongAccumulator提供了自定义的函数操作

public class ClickNumberDemo {

    int number = 0;

    public synchronized void add_synchronized() {
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();

    public void add_atomicInteger() {
        atomicInteger.incrementAndGet();
    }

    AtomicLong atomicLong = new AtomicLong();

    public void add_atomicLong() {
        atomicLong.incrementAndGet();
    }

    LongAdder adder = new LongAdder();

    public void add_longAdder() {
        adder.increment();
    }

    LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void add_longAccumulater() {
        accumulator.accumulate(1);
    }
}

class LongAdderCalcDemo {
    public static final int SIZE_THREAD = 50;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(SIZE_THREAD);
        ClickNumberDemo clickNumber = new ClickNumberDemo();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100000; j++) {
                        clickNumber.add_longAdder();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis() + "毫秒" + "t");
        System.out.println(clickNumber.adder.longValue());

    }
}

通过测试我们得出LongAdder耗时最少,性能最高。在实际的开发过程中建议使用LongAdder。

👉 如果本文对你有帮助的话,欢迎点赞|在看,非常感谢

原文始发于微信公众号(阿福聊编程):浅谈JUC原子类

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/105802.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!