并发安全的特性 -- 原子性:Atomic包

并发安全的特性

定义: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的

主要体现在下面三个方面:

1.原子性: 提供了互斥访问,同一时刻只能有一个线程对它进行操作。

2.可见性: 一个线程对主内存的修改可以及时被其它线程观察到。

3.有序性: 一个线程观察其它线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无章。
e

原子性:Atomic包 基于CAS操作

1、基本类型AtomicXXX

AtomicXXX : CAS、 java.util.concurrent(我们常说的JUC),该包提供了对Java中多种基本类型安全的操作方法。
其中用到了一个非常重要的方法,Unsafe.compareAndSwapXXX方法,就是我们常说的CAS操作了。

// 声明一个AtomicInteger 对象
static AtomicInteger count = new AtomicInteger(0);

//加一操作(单纯的进行加一操作是没有区别的)
count.incrementAndGet();//返回值是先加后获取
count.getAndIncrement();//返回值是先获取后加

这里看一下这两个方法的源码,在底层是如何实现的:

//返回值是先加后获取
public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//返回值是先获取后加
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
}

//var5是使用getIntVolatile()方法从底层获取当前对象的值
//compareAndSwapInt()方法,var2的值和var5的值是否相等,相等就执行相加的操作,如果不相等就把var5的值赋给var2,之后重新判断
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}

这两个方法是Java底层实现的,被native修饰
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public native int getIntVolatile(Object var1, long var2);

  在JDK8中又新增了一个类LongAdder,相对于Atomic包中的AtomicLong有它自己的优点。我们知道在CAS的底层实现中,是在一个死循环中不断的去判断值与底层是否相等。
  显然,在竞争不激烈的情况下修改成功的概率很高,但是如果竞争激烈的话,情况就不容乐观了,性能会受到一定的影响。而LongAdder可以在低并发的情况下与AtomicLong性能保持一致,在高并发的情况下,通过分散数据来得到较为客观的性能。
  所以在低并发的情况下,优先使用AtomicLong,在高并发的情况下,优先使用LongAdder。并且,如果是在生成序列号这种全局唯一的,还是推荐使用AtomicLong来保证正确性。

二、数组类型AtomicXXXArray

主要有以下三种原子性数组:

  1.AtomicLongArray: 提供对int[]数组元素的原子性更新操作。

  2.AtomicIntegerArray: 提供对long[]数组元素的原子性更新操作。

  3.AtomicReferenceArray: 提供对引用类型[]数组元素的原子性更新操作。


//这里主要介绍两个通用方法

//懒赋值,具有原子性但不具有可见性
atomicIntegerArray.lazySet(i,newValue);

//比较当前值和期望值是否相等,相等则更新为新的值,具有原子性和可见性
atomicIntegerArray.compareAndSet(i,expect,update);

三、引用类型

主要有以下三种原子性引用类:

  1.AtomicReference: 引用类型原子类

  2.AtomicStampedRerence: 原子更新引用类型里的字段原子类

  3.AtomicMarkableReference : 原子更新带有标记位的引用类型

AtomicReference:

看下面这一段代码,可以猜一下最终的运行结果


private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) throws Exception {
        count.compareAndSet(0,2);
        count.compareAndSet(0,1);//no
        count.compareAndSet(1,3);//no
        count.compareAndSet(2,4);
        count.compareAndSet(3,1);//no
        log.info("count:{}",count);
    }
控制台打印结果:
count:5000

  这就是AtomicReference类重要的一个方法,可以根据实际的值和期望的值是否相等来判断是否要进行更新操作。

三、对象的属性修改类型

  1.AtomicIntegerFieldUpdater: 原子更新整形字段的更新器

  2.AtomicLongFieldUpdater: 原子更新长整形字段的更新器

  3.AtomicStampedReference : 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

AtomicIntegerFieldUpdater:
private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");

//要求该字段必须被volatile修饰,且不能被static修饰
    @Getter
    public volatile int count = 100;

    private static AtomicExample5 atomicExample5 = new AtomicExample5();

    public static void main(String[] args) throws Exception {
        if(updater.compareAndSet(atomicExample5,100,120)){
            log.info("updater success, {}",atomicExample5.getCount());
        }
        if(updater.compareAndSet(atomicExample5,100,120)){
            log.info("updater success, {}",atomicExample5.getCount());
        }else{
            log.info("updater failed, {}",atomicExample5.getCount());
        }
    }
    
控制台打印结果:
updater success, 120
updater failed, 120

整体上来说和AtomicReference类基本上差不多,该类可以实现根据类的某一个具体变量的值来控制更新操作,更加方便。但是,要求该字段必须被volatile修饰,且不能被static修饰。

AtomicStampedReference:CAS的ABA问题

  **ABA问题:**指的是当一个线程X对变量进行操作的时候,另一个一个线程Y将变量值从A改成的B,又将B改回了A,这时线程X进行CAS操作是没有问题的,但是这已经违背了设计思想。
  比如在这种情况下:
在这里插入图片描述
  线程T1希望将栈顶替换为B,但是这时线程T2对栈进行了如下操作,将栈变成了A->C->D。
在这里插入图片描述
  这时线程T1再去进行操作,因为栈顶元素还是A,所以会继续进行,此时栈顶元素B指向null,从而使得C和D被平白无故的丢失了。
  所以为了解决这一问题,就推出了AtomicStampedRerence类。

/**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

  这是这个类提供的compareAndSet方法,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。

AtomicBoolean

  希望某一段代码只执行一次,可以使用这个类来实现。

//请求总数
    public static int clientTotal = 5000;

    //同时并发执行的线程数
    public static int threadTotal = 50;

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.info("Exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}",isHappened.get());
    }

    private static void test(){
    	//如果希望的值是false则更新为true,并返回true
        if(isHappened.compareAndSet(false,true)){
            //要执行一次的代码
            log.info("execute");
        }
    }

执行结果:
execute
isHappened:true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值