并发与高并发(七)-线程安全性-原子性-atomic

前言

何为原子性?它又是通过什么原理来控制线程安全的?这里主要介绍有关Atomic原子性操作的几个类的使用场景和方法。

主体概要

  • AtomicInteger

  • AtomicLong

  • LongAdder

  • AtomicReference

  • AtomicIntegerFieldUpdater

  • AtomicStampedReference

  • AtomicBoolean

主体内容

一、线程安全性定义

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

线程安全性主要体现在三个方面:原子性、可见性、有序性:

  • 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
  • 可见性:一个线程对主内存的修改可以及时地被其他线程观察到
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

使用示例:

1.AtomicInteger

package com.controller.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

import com.annoations.ThreadSafe;

import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicIntegerTest {
    //请求数
    public static int clientTotal=5000;
    //并发数
    public static int threadTotal=200;
    //计数值
    public static AtomicInteger count= new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException{
        //创建线程池
        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();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}",count.get());
    }
    
    private static void add(){
        //先增加操作,再获取当前的值
        count.incrementAndGet();
        //先获取当前的值,在增加操作
        //count.getAndIncrement();
    }
}

重点在于.incrementAndGet()方法中的unsafe.getAndAddInt()方法,如下所示:

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

使用了一个叫unsafe的类,看它里面getAndAddInt的实现,标红的compareAndSwapInt方法需要注意,首先这个“paramObject”是指当前调用的对象,即上面例子中的count对象,而paramLong是当前的值,比如这里想执行2+1=3的操作,paramLong就等于2,paramInt就等于1。i是从调用这个getIntVolatile方法得到底层当前的值。那么compareAndSwapInt(paramObject, paramLong, i, i + paramInt))中变量值可以解释为compareAndSwapInt(count,2, 2, 2 +1)),如果paramLong和底层的i值相同的话,将其值更新为i+paramInt。也就是期望的值与底层值相等,才会执行+1操作,最后把底层的值覆盖掉,这就是CAS的核心。

 public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
   {
    int i;
     do
    {
       i = getIntVolatile(paramObject, paramLong);
     } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
     return i;
   }

2.AtomicLong

package com.controller.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;

import com.annoations.ThreadSafe;

import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicLongTest {
    //请求数
    public static int clientTotal=5000;
    //并发数
    public static int threadTotal=200;
    //计数值
    public static AtomicLong count= new AtomicLong(0);
    
    public static void main(String[] args) throws InterruptedException{
        //创建线程池
        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();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}",count.get());
    }
    
    private static void add(){
        //先增加操作,再获取当前的值
        count.incrementAndGet();
        //先获取当前的值,在增加操作
        //count.getAndIncrement();
    }
}

3.LongAdder

package com.controller.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LongAddrTest {
    //请求数
        public static int clientTotal=5000;
        //并发数
        public static int threadTotal=200;
        //计数值
        public static LongAdder count= new LongAdder();
        
        public static void main(String[] args) throws InterruptedException{
            //创建线程池
            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();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count);
        }
        
        private static void add(){
            //先增加操作,再获取当前的值
            count.increment();
            //先获取当前的值,在增加操作
            //count.getAndIncrement();
        }
}

LongAdder 与 AtomicLong有什么区别?

(1)AtomicLong 是基于 CAS 方式自旋更新的;LongAdder 是把 value 分成若干cell,并发量低的时候,直接 CAS 更新值,成功即结束。并发量高的情况,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋 CAS 更新 cell值。取值的时候,调用 sum() 方法进行每个cell累加。

(2)AtomicLong 包含有原子性的读、写结合的api;LongAdder 没有原子性的读、写结合的api,能保证结果最终一致性。
低并发场景AtomicLong 和 LongAdder 性能相似,高并发场景 LongAdder 性能优于 AtomicLong。

4.AtomicReference

package com.controller.atomic;
import java.util.concurrent.atomic.AtomicReference;
import com.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicReferenceTest {
    private static AtomicReference<Integer> count = new AtomicReference<>(0);
    
    public static void main(String[] args){
        //如果值为0,则结果为2
        count.compareAndSet(0, 2);//2
        //如果值为0,则结果为1
        count.compareAndSet(0, 1);//no
        //如果值为1,则结果为3
        count.compareAndSet(1, 3);//no
        //如果值为2,则结果为4
        count.compareAndSet(2, 4);//4
        //如果值为3,则结果为5
        count.compareAndSet(3, 5);//no
        log.info("count:{}",count.get());
    }
}

5.AtomicIntegerFieldUpdater

package com.controller.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AtomicIntegerFileUpdaterTest {
    
    private static AtomicIntegerFieldUpdater<AtomicIntegerFileUpdaterTest> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFileUpdaterTest.class, "count");
    @Getter
    public volatile int count=100;
    
    public static void main(String[] args){
        AtomicIntegerFileUpdaterTest AtomicIntegerFileUpdaterTest = new AtomicIntegerFileUpdaterTest();
        
        if(updater.compareAndSet(AtomicIntegerFileUpdaterTest, 100, 120)){
            log.info("update success,{}",AtomicIntegerFileUpdaterTest.getCount());
        }
        if(updater.compareAndSet(AtomicIntegerFileUpdaterTest, 100, 120)){
            log.info("update success,{}",AtomicIntegerFileUpdaterTest.getCount());
        }else{
            log.info("update failed,{}",AtomicIntegerFileUpdaterTest.getCount());
        }
    }
}

加粗部分指代的是当前类"AtomicIntegerFileUpdaterTest"下的“count”变量,而count必须被volatile关键字修饰,不能被static修饰。

程序执行的结果为:

update success,120
update failed,120

compareAndSet(param1,param2,param3)指代的是如果param1中被AtomicIntegerFieldUpdater定义的对象的变量等于param2,则将其更新为param3。否则不更新。

6.AtomicStampedReference

解决CAS的ABA问题,每次增加操作,在原有版本号基础上加一。

关于ABA问题,这里作一个详细介绍:

在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
1.线程1,期望值为A,欲更新的值为B
2.线程2,期望值为A,欲更新的值为B
线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。

ABA问题带来的危害:

小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
线程1(提款机):获取当前值100,期望更新为50,
线程2(提款机):获取当前值100,期望更新为50,
线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
线程3(默认):获取当前值50,期望更新为100,
这时候线程3成功执行,余额变为100,
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。
解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。

其中的compareAndSet()方法如下:

 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)));
    }

7.AtomicBoolean

package com.controller.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AtomicBooleanTest {
    private static AtomicBoolean isHappend = new AtomicBoolean(false);
    //请求数
    public static int clientTotal=5000;
    //并发数
    public static int threadTotal=200;
    
    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.error("exception",e);
                        }
                        countDownLatch.countDown();
                    });
                }
                countDownLatch.await();
                executorService.shutdown();
                log.info("isHappend:{}",isHappend.get());
    }
    public static void test(){
        if(isHappend.compareAndSet(false, true)){
            log.info("execute..");
        }
    }
}

结果:true

这个类的示例用于在并发情况下,想只执行一次某段逻辑,绝对不会重复。

posted @ 2019-11-17 02:50  mcbbss  阅读(444)  评论(0编辑  收藏  举报