必威体育Betway必威体育官网
当前位置:首页 > IT技术

atomic的实现原理

时间:2019-10-04 07:41:07来源:IT技术作者:seo实验室小编阅读:75次「手机版」
 

atomic

 

在多线程的场景中,我们需要保证数据安全,就会考虑同步的方案,通常会使用synchronized或者lock来处理,使用了synchronized意味着内核态的一次切换。这是一个很重的操作。

有没有一种方式,可以比较便利的实现一些简单的数据同步,比如计数器等等。concurrent包下的atomic提供我们这么一种轻量级的数据同步的选择。

class MyThread implements Runnable {

    static Atomicinteger ai=new AtomicInteger(0);
 
    public void run() {
        for (int m = 0; m < 1000000; m++) {
            ai.getAndIncrement();
        }
    }
};
 
public class TestAtomicInteger {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
 
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        t1.start();
        t2.start();
        Thread.sleep(500);
        System.out.println(MyThread.ai.get());
    }
}

在以上代码中,使用AtomicInteger声明了一个全局变量,并且在多线程中进行自增,代码中并没有进行显示的加锁。以上代码的输出结果,永远都是2000000。如果将AtomicInteger换成Integer,打印结果基本都是小于2000000。

也就说明AtomicInteger声明的变量,在多线程场景中的自增操作是可以保证线程安全的。接下来我们分析下其原理。

原理

这里,我们来看看AtomicInteger是如何使用非阻塞算法来实现并发控制的。

AtomicInteger的关键域只有一下3个:

public class AtomicInteger extends Number implements java.io.serializable {
    private static final long serialversionuid = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueoffset;

    static {
        try {
            valueOffset = unsafe.objectfieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new ERROR(ex); }
    }

    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

    ......
}

这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具

valueOffset是用来记录value本身在内存的编译地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。

value是用来存储整数的时间变量,这里被声明为volatile。volatile只能保证这个变量的可见性。不能保证他的原子性。

可以看看getAndIncrement这个类似i++的函数,可以发现,是调用了UnSafe中的getAndAddInt。

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

    /**
     * Atomically sets to the given value and returns the old value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

    public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
            //使用unsafe的native方法,实现高效的硬件级别CAS
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }

如何保证原子性:自旋 + CAS(乐观锁)。在这个过程中,通过compareAndSwapInt比较更新value值,如果更新失败,重新获取旧值,然后更新。

优缺点

CAS相对于其他锁,不会进行内核态操作,有着一些性能的提升。但同时引入自旋,当锁竞争较大的时候,自旋次数会增多。cpu资源会消耗很高。

换句话说,CAS+自旋适合使用在低并发有同步数据的应用场景。

Java 8做出的改进和努力

在Java 8中引入了4个新的计数器类型,LongAdder、Longaccumulator、DoubleAdder、DoubleAccumulator。他们都是继承于striped64。

在LongAdder 与AtomicLong有什么区别?

Atomic*遇到的问题是,只能运用于低并发场景。因此LongAddr在这基础上引入了分段锁的概念。可以参考《JDK8系列之LongAdder解析》一起看看做了什么。

大概就是当竞争不激烈的时候,所有线程都是通过CAS对同一个变量(Base)进行修改,当竞争激烈的时候,会将根据当前线程哈希到对于Cell上进行修改(多段锁)。

    /**
     * Table of cells. When non-null, size is a power of 2.
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
    transient volatile long base;

    /**
     * Padded variant of AtomicLong supporting only raw accesses plus CAS.
     *
     * JVM intrinsics note: It would be possible to use a release-only
     * form of CAS here, if it were provided.
     */
    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

可以看到大概实现原理是:通过CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁)增加并发性能。

Refrence:

https://mp.weixin.qq.com/s/aw6OXC9wkxH42rCywNd7yQ

相关阅读

tracert/traceroute原理

一、路由追踪程序traceroute/tracert Traceroute是Linux和Mac OS等系统默认提供的路由追踪小程序,Tracert是Windows系统默认提供

简单概括一下《金字塔原理》的主要内容?

本文来自 网易云社区 。为大家分享一篇网易教育产品部韩坤芳关于金字塔原理的文章。希望一起交流探讨,共同进步提升。工作的时候

裂变式传播:定义、原理及方法论

裂变式传播,最重要是抓住用户心理,提供足够的传播动力,在每个环节降低用户操作门槛。一、裂变式传播定义我第一次接触“裂变”这个概

pika的原理和实现

Pika  pika是360奇虎公司开源的一款类redis存储系统,主要解决的是用户使用 Redis 的内存大小超过 50G、80G 等等这样的情况,会遇

卡尔曼滤波(Kalman Filter)原理理解和测试

Kalman Filter学原理学习 1. Kalman Filter 历史 Kalman滤波器的历史,最早要追溯到17世纪,Roger Cotes开始研究最小均方问题

分享到:

栏目导航

推荐阅读

热门阅读