AtomicLong 介绍
可以原子更新的 Long 值。
AtomicLong用于诸如原子递增的序列号之类的应用程序中,并且不能用作Long的替代品。
但是,此类确实扩展了Number,以允许通过处理基于数字的类的工具和实用程序进行统一访问。
API
直接查阅 JDK Doc
AtomicLong 与 Long 的区别
这两个对象之间存在显着差异,虽然最终结果是相同的,但它们肯定是非常不同的,并且在非常不同的情况下使用。
Long
在以下情况下使用基本Long对象:
-
你需要包装类
-
您正在使用一个集合
-
你只想处理对象而不是 primitives(有点工作)
AtomicLong
您在以下情况下使用AtomicLong:
-
您必须保证该值可以在并发环境中使用
-
你不需要包装类(因为这个类不会自动装箱)
-
Long本身不允许线程互操作,因为两个线程都可以看到并更新相同的值,但是使用AtomicLong,在多个线程将看到的值周围有相当不错的保证。
实际上,除非您曾经使用线程,否则您不需要使用AtomicLong。
AtomicLong 使用
线程安全的构建一个递增序列号。
代码
static class Counter {
private AtomicLong c = new AtomicLong(0);
public void increment() {
c.getAndIncrement();
}
public long value() {
return c.get();
}
}
测试
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());
}
- 测试结果
Final number (should be 1000): 1000
AtomicLong 源码
基于 JDK 1.8.0_191
UnSafe
源码中一个比较重要的对象 Unsafe
// 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变量修饰。
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值
/**
* 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原子性设定为给出值并且返回旧值
/**
* 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。
/**
* 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重新获取当前值。
/**
* 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
出处
/**
* 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的代码中可以发现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,延后了删除其他线程中的缓存这一步。
#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;//设置值
}
总结
-
底层还是 C
-
要知道应用的场景。
拓展阅读
参考资料
what-is-atomiclong-in-java-used-for
https://www.tutorialspoint.com/java_concurrency/concurrency_atomiclong.htm
- 源码