JUC之atomic

Atomic

简介

atomic包下原子操作类提供了一种用法简单、性能高效 、线程安全地更新一个变量的方式。

atomic包下一共有12个相关的类,分为4组,分别用于原子更新基本类型,原子更新数组,原子更新引用,原子更新字段。

使用

  • 原子更新基本类型

    AtomicBoolean AtomicInteger AtomicLong
    常用方法,以AtomicInteger为例

    • int get() 获取实际的值
    • void set(int newValue) 设置值,多线程情况下会导致值不安全
    • boolean compareAndSet(int expect, int update) 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值,并返回是否成功
    • int getAndSet(int newValue) 以原子的方式将值设置为新值
    • int getAndIncrement() 以原子的方式将值加一,并返回
      以上方法大致可以分为3类:第一类不保证多线程操作下线程安全,如set();第二类返回boolean类型的值,需要传入两个参数,预期值和更新值,如compareAndSet(),这类方法不保证值一定能更新成功,多个线程同时操作,同样的入参只会有一个线程成功。第三类:getAndIncrement(),这类方法会保证多线程下操作一定成功,它不关心预期的值,只关心更新后的值,假如多个线程同时操作,同一时刻只有一个线程在更新值,其他线程会循环等待之更新成功。
  • 原子更新数组

    AtomicIntegerArray AtomicLongArray AtomicReferenceArray
    常用方法,同样以AtomicIntegerArray为例

    • int get(int i) 获取第i个数组下标中的值
    • void set(int i, int newValue) 设置第i个数组下标中的值
    • boolean compareAndSet(int i, int expect, int update) 输入的第i个下标等于预期值,则以原子方式将该值设置为输入的值,并返回是否成功
    • int getAndSet(int i, int newValue) 以原子的方式将数字第i的值设置为新值
    • int getAndIncrement(int i) 以原子的方式将第i的值加一,并返回
  • 原子更新字段

    AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
    常用方法,同样以AtomicIntegerFieldUpdater为例

    • AtomicIntegerFieldUpdaterImpl(final Class tclass,final String fieldName,final Class<?> caller) 构造函数需要传入class 和 字段名称
    • void set(T obj, int newValue); 类似,不再说明
    • boolean compareAndSet(T obj, int expect, int update) 类似,不再说明
    • int getAndSet(T obj, int newValue) 类似,不再说明
  • 原子更新引用

    AtomicReference AtomicMarkableReference AtomicStampedReference
    常用方法,以AtomicReference为例

    • void set(V newValue)
    • boolean compareAndSet(V expect, V update)
    • V getAndSet(V newValue)

代码示例:

多线程下的并发问题

public class SatomicExample1 {

    private static int sum;
    private static AtomicInteger atomicSum = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch c1 = new CountDownLatch(5);
        CountDownLatch c2 = new CountDownLatch(5);


        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                for(int j = 0; j < 100000; j++) {
                    sum++;
                }
                c1.countDown();
            });
        }

        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                for(int j = 0; j < 100000; j++) {
                    atomicSum.getAndIncrement();
                }
                c2.countDown();
            });
        }
        c1.await();
        c2.await();
        System.out.println(sum);
        System.out.println(atomicSum.get());
        executorService.shutdown();

    }
}

输出结果

494796
500000

使用atomic实现简单的自旋锁

public class SatomicExample2 {

    private static int sum;

    public static void main(String[] args) throws InterruptedException {
        TestLock lock = new TestLock();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        CountDownLatch c1 = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                for(int j = 0; j < 100000; j++) {
                    try {
                        lock.lock();
                        sum++;
                    } finally {
                        lock.unLock();
                    }
                }
                c1.countDown();
            });
        }
        c1.await();
        System.out.println(sum);
        executorService.shutdown();
    }
}
class TestLock {

    private AtomicBoolean locked = new AtomicBoolean();

    public void lock() {
        while (!locked.compareAndSet(false, true)) {}
    }

    public void unLock() {
        locked.set(false);
    }

}

结果

500000

lazySet

我们可以观察到,大多数Atomic类中都有lazySet()这个方法,这个方法有什么用呢?以AtomicInteger为例,通过观察源码可以发现,变量value是通过volatile修饰的,volatile在保证value可见性的同时也会比不加volatile修饰的更浪费cpu性能。我们知道volatile是通过设置内存屏障来实现的,对于某些确定不需要加内存屏障的情况下,volatile势必会浪费性能。所以Doug Lea大神提供也lazySet()这个方法提供了一个可优化的选项。当然,如果使用错误,会导致一些很严重的问题。

ABA 问题

假如一个值原来是A,变成了B,又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。

通过引入 AtomicStampedReference AtomicMarkableReference来解决问题

AtomicStampedReference 提供了boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) ,在原有需要提供预期值,更新值之外,还需要提供预期版本号和更新版本号,假如预期值符合预期,允许更新,但是版本号不符合预期,同样不会更新成功。

AtomicStampedReferenceAtomicMarkableReference的区别是
AtomicStampedReference关注最新的版本号,而AtomicMarkableReference只关注最新是否更改过,换个说法假如一个更新的过程是这样的A->B->A->B->A,AtomicStampedReference关注最后的A到底是哪个版本号的A,而AtomicMarkableReference只关注A是否产生了ABA问题

原理

我们以AtomicInteger为例

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看出都是调用了unsafe的方法
Unsafe源码

    /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);


    /**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object <code>o</code>
     * at the given <code>offset</code>.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

可以看出getAndAddInt()就是通过自旋compareAndSwapInt()来实现的,接着看compareAndSwapInt()

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

最终又是通过 Atomic::cmpxchg来实现的

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

最后我们终于看到了最原始的汇编指令cmpxchg

cpmxchg

cmpxchg是汇编指令
作用:比较并交换操作数.
如:CMPXCHG r/m,r 将累加器AL/AX/EAX/RAX中的值与首操作数(目的操作数)比较,如果相等,第2操作数(源操作数)的值装载到首操作数,zf置1。如果不等, 首操作数的值装载到AL/AX/EAX/RAX并将zf清0
该指令只能用于486及其后继机型。第2操作数(源操作数)只能用8位、16位或32位寄存器。第1操作数(目地操作数)则可用寄存器或任一种存储器寻址方式。

相关资料

openjdk 源码下载

Doug Lea的cookbook

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值