AtomicLong 介绍

AtomicLong

可以原子更新的 Long 值。

AtomicLong用于诸如原子递增的序列号之类的应用程序中,并且不能用作Long的替代品。

但是,此类确实扩展了Number,以允许通过处理基于数字的类的工具和实用程序进行统一访问。

API

直接查阅 JDK Doc

AtomicLong 与 Long 的区别

这两个对象之间存在显着差异,虽然最终结果是相同的,但它们肯定是非常不同的,并且在非常不同的情况下使用。

Long

在以下情况下使用基本Long对象:

  1. 你需要包装类

  2. 您正在使用一个集合

  3. 你只想处理对象而不是 primitives(有点工作)

AtomicLong

您在以下情况下使用AtomicLong:

  1. 您必须保证该值可以在并发环境中使用

  2. 你不需要包装类(因为这个类不会自动装箱)

  3. Long本身不允许线程互操作,因为两个线程都可以看到并更新相同的值,但是使用AtomicLong,在多个线程将看到的值周围有相当不错的保证。

实际上,除非您曾经使用线程,否则您不需要使用AtomicLong。

原子性

AtomicLong 使用

线程安全的构建一个递增序列号。

代码

  [java]
1
2
3
4
5
6
7
8
9
static class Counter { private AtomicLong c = new AtomicLong(0); public void increment() { c.getAndIncrement(); } public long value() { return c.get(); } }

测试

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(final String[] arguments) throws InterruptedException { final Counter counter = new Counter(); //1000 threads for(int i = 0; i < 1000 ; i++) { new Thread(new Runnable() { public void run() { counter.increment(); } }).start(); } Thread.sleep(6000); System.out.println("Final number (should be 1000): " + counter.value()); }
  • 测试结果
  [plaintext]
1
Final number (should be 1000): 1000

AtomicLong 源码

基于 JDK 1.8.0_191

UnSafe

源码中一个比较重要的对象 Unsafe

  [java]
1
2
// setup to use Unsafe.compareAndSwapLong for updates private static final Unsafe unsafe = Unsafe.getUnsafe();

先列一下AtomicLong里面用到的Unsafe方法,实际上这里的Long也有Object或者Integer版本:

  • compareAndSwapLong

  • getAndAddLong

  • getAndSetLong

  • putOrderedLong

构造函数与内部变量与静态块

可以看到在类加载时,需要获取Unsafe的实例,检查JVM是否支持无锁的long型CAS操作,获取类中value字段的偏移量。

构造函数可以指定初始值也可以默认为0。value值用volatile变量修饰。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class AtomicLong extends Number implements java.io.Serializable { private static final long serialVersionUID = 1927816293512124184L; // setup to use Unsafe.compareAndSwapLong for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; /** * Records whether the underlying JVM supports lockless * compareAndSwap for longs. While the Unsafe.compareAndSwapLong * method works in either case, some constructions should be * handled at Java level to avoid locking user-visible locks. */ static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); /** * Returns whether underlying JVM supports lockless CompareAndSet * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS. */ private static native boolean VMSupportsCS8(); static { try { valueOffset = unsafe.objectFieldOffset (AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile long value; /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public AtomicLong(long initialValue) { value = initialValue; } /** * Creates a new AtomicLong with initial value {@code 0}. */ public AtomicLong() { } }

Getter & Setter

get与set是直接获取与更新value值

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** * Gets the current value. * * @return the current value */ public final long get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(long newValue) { value = newValue; }

而getAndSet原子性设定为给出值并且返回旧值

  [java]
1
2
3
4
5
6
7
8
9
/** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final long getAndSet(long newValue) { return unsafe.getAndSetLong(this, valueOffset, newValue); }

compareAndSet

compareAndSet如果当前value==expect,则原子性的更新value为update,返回是否操作成功,同样借助了unsafe.compareAndSwapLong。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); }

getAndAdd和addAndGet

下面几个方法本质上都是一个意思,原子性的增减当前值,返回新值或者旧值。

全部都是基于Unsafe.getAndAddLong,实现方法是循环尝试compareAndSwapLong,如果因为有其他线程更新而失败则getLongVolatile重新获取当前值。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/** * Atomically increments by one the current value. * * @return the previous value */ public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); } /** * Atomically decrements by one the current value. * * @return the previous value */ public final long getAndDecrement() { return unsafe.getAndAddLong(this, valueOffset, -1L); } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final long getAndAdd(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta); } /** * Atomically increments by one the current value. * * @return the updated value */ public final long incrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L; } /** * Atomically decrements by one the current value. * * @return the updated value */ public final long decrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L; } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */ public final long addAndGet(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta) + delta; }

LazySet

出处

  [java]
1
2
3
4
5
6
7
8
9
/** * Eventually sets to the given value. * * @param newValue the new value * @since 1.6 */ public final void lazySet(long newValue) { unsafe.putOrderedLong(this, valueOffset, newValue); }

为一个AtomicLong对象设置一个值,jvm会确保其他线程读取到最新值,原子类和voliatile变量也是一样的,这是由依赖于硬件的系统指令(如x86的xchg)实现的。lazySet却是无法保证这一点的方法,所以其他线程在之后的一小段时间里还是可以读到旧的值。

lazySet是使用Unsafe.putOrderedObject方法,这个方法在对低延迟代码是很有用的,它能够实现非堵塞的写入,这些写入不会被Java的JIT重新排序指令(instruction reordering),这样它使用快速的存储-存储(store-store) barrier, 而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的写操作上,这种性能提升是有代价的,写后结果并不会立即被其他线程甚至是自己的线程看到,通常是几纳秒后被其他线程看到,这个时间比较短,所以代价可以忍受。

优点

性能:在多核处理器下,内存以及cpu缓存的读和写常常是顺序执行的,所以在多个cpu缓存之间同步一个内存值的代价是很昂贵的。

如何实现

大多数的原子类,比如AtomicLong本质上都是一个Unsafe和一个volatile Long变量的包装类。

值得注意的是AtomicLong.lazySet方法实际是调用了本地方法Unsafe.putOrderedLong,本地方法Unsafe.putOrderedLong的实现可以参考

unsafe.cpp

从Unsafe的代码中可以发现Unsafe_setOrderedLong是一个本地方法(c++实现),它仅调用了SET_FIELD_VOLATILE,这很是奇怪,我们期望共享的Unsafe_setLongVolatile拥有不同的语义。

PS:在非增强版本中,setOrdered仅仅是调用了setVolatile方法,很是让人失望。深入查看你会发现其实他们是相同的,SET_FIELD_VOLATILE是一个OrderAccess:release_store_fence的包装。

可以在Linux x86的代码 orderAccess_linux_x86.inline.hpp 中找到此方法的实现,在64bit x86系统中采用xchgq来代码,64位版本指令的问题我上面有提到过。

ps:从理论上讲lazySet能比一个标准的volatile变量的写性能更好。但是我在openJdk里没有找到相关代码。

Felix Sulima说:

sun.misc.unsafe很多方法被jvm增强了,JIT(just in time运行时编译执行的技术)直接解释而忽略原始的实现。

可以在这里找到这个例子:

src/share/vm/classfile/vmSymbols.hpp@3facbb14e873 列表中的native方法仅仅是非JIT环境下的一个备份的内部方法。

例如,如果它没有被调用(我也不知道是什么原因),因此这些方法缺乏一些必要的优化。

从Talk from JAX London的幻灯片11-12可以看到AtomicLong.lazySet(…)在x86系统上会被编译成“mov”指令。

这里是Google Group里关于如何获得JIT装配的一个描述。

源码

下面是native方法的实现,因为OpenJDK版本异常复杂,可以看下面libjava版本的。

关于spinlock lock这段,个人理解是自旋等待其他线程结束占用,然后修改内存中value的值。相比直接修改value,延后了删除其他线程中的缓存这一步。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define SET_FIELD_VOLATILE(obj, offset, type_name, x) \ oop p = JNIHandles::resolve(obj); \ OrderAccess::release_store_fence((volatile type_name*)index_oop_from_field_offset_long(p, offset), x); UNSAFE_ENTRY(void, Unsafe_SetOrderedLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong x)) UnsafeWrapper("Unsafe_SetOrderedLong"); #ifdef SUPPORTS_NATIVE_CX8 SET_FIELD_VOLATILE(obj, offset, jlong, x); #else // Keep old code for platforms which may not have atomic long (8 bytes) instructions { if (VM_Version::supports_cx8()) { SET_FIELD_VOLATILE(obj, offset, jlong, x);//指令设置 } else { Handle p (THREAD, JNIHandles::resolve(obj)); jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset)); ObjectLocker ol(p, THREAD); *addr = x; } } #endif UNSAFE_END void sun::misc::Unsafe::putOrderedLong (jobject obj, jlong offset, jlong value) { volatile jlong *addr = (jlong *) ((char *) obj + offset);//计算value字段地址 spinlock lock;//自旋 *addr = value;//设置值 }

总结

  1. 底层还是 C

  2. 要知道应用的场景。

拓展阅读

参考资料

jdk8 官方文档

what-is-atomiclong-in-java-used-for

https://www.tutorialspoint.com/java_concurrency/concurrency_atomiclong.htm

  • 源码

AtomicLong.lazySet是如何工作的?

Java多线程——AtomicLong LongAdder源码解析

AtomicLong与LongAdder性能对比