Java并发编程之原子性-Atomic源码详解

1、Atomic中存在Atmomicxxx的类,都是通过CAS来实现原子性的。

对于平时适用count++问题,count++并不是线程安全的,所以在多线程情况下,适用count++会出现得到的值并不是我们期望的值。

问题如下:

在这里插入图片描述

所以为了解决此类问题我们需要用到Atomic,例如我们可以适用AtomicInteger来代替count++操作,保证线程安全。

例子如下:

/**
 * @author v_vllchen
 */

public class AtomicExample {
    private static final Logger LOGGER = LoggerFactory.getLogger(AtomicExample.class);
    // 请求总数
    public static int clientTotal = 5000;

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

    public static AtomicInteger count = new AtomicInteger(0);

    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();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                    LOGGER.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count:{}"+count.get());
        LOGGER.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
    }
}

代码来自https://blog.csdn.net/qq_34871626/article/details/81411815

在该代码中我们可以看到我们请求的总数是5000,并发执行的线程数为200,通过累加对AtomicInteger进行累加可以看到最终的结果是5000;

在这里插入图片描述

以上我们可以看到适用Atomic的确解决了count++的线程安全问题,但是底层如何通过CAS来实现的这样一个过程呢,还是要通过源码来解释

1. 在add()方法中有一个incrementAndGet方法,我们可以进入这个源码内部

在这里插入图片描述

看到它调用了unsafe类下面的getAndAddInt方法,这个方法中有三个参数,第一个参数是对象本身、第二个参数是valueOffset用来记录当前value在内存中的编译地址,第三个参数为常量

2. 继续深入查看unsafe中的getAndAddInt方法,

在这里插入图片描述

从第一个步中我们知道了var1是对象本身,var2是value在内存中的编译地址,var4是一个常数。 该方法中有两个方法:

1、getIntVolatile(),该方法通过var1以及var2来找到内存中的值,该方法是一个native方法,并不是Java实现的。

2、compareAndSwapInt(),在方法中通过while语句来实现compareAndSwapInt()方法,该方法是用来比较var1与getIntVolatile()底层返回的value,如果相同则把var5更新为var5+var4,如果不相同,则循环通过getIntVolatile()获取底层的value值,直到当前的var1与底层的返回的value相同才做更新。

compareAndSwapInt()方法的核心就是通常所说的CAS。

以上就是Atomic的实现思想

但是对于CAS来说会遇到一个经常性的问题就是ABA问题(线程1将值A改成B,接着又改回了A,其它线程通过与值做比较时发现值没有改变,则直接进行更改,实际上值已经被更改了)。

所以对于这种问题,在Atomic中通过AtomicStampReference类来解决ABA问题。ABA问题的解决办法就是在线程每次去更新的时候将版本号加一,这样其它线程通过版本号检测该值是否被更新过。

public class AtomicStampedReference<V> {
 
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
 
   ... 此处省略多个方法 ....
 
   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方法中通过比较stamp这个值,来确定线程的值是否被更新。

结语:

算是对以前面试知识点的查漏补缺吧,有次面试问到这个Atomic,自己不懂,但是自己本着不能不提供解决方案的思想,在多线程问题上提出了使用ConcurrentHashMap来实现面试官提出的问题,但是得到了一个杀鸡焉用牛刀的评价,对于自己缺失的知识还是要及时做补充。

我是一个走在学习路上、膜拜大佬的程序员!

在这里插入图片描述

发布了92 篇原创文章 · 获赞 7 · 访问量 7538

猜你喜欢

转载自blog.csdn.net/qq_40126996/article/details/105224438