1、为什么要用AtomicLong
原始的数据类型long和Long,没有实现同步,在多线程的情况下会出现错误。有人会说为什么不采用volatile关键字呢?我们知道volatile可以保证数据在不同线程之间的可见性,但是volatile又一个缺点是不保证原子性,正是这个原因无法保证每次更新操作都是正确的。
正是上面的原因,jdk1.5之后出现了AtomicLong,实现了long的原子操作。
2、AtomicLong分析
来看看AtomicLong实现了哪些原子操作。
1)getAndIncrement()
获得数据的同时,进行自增操作。
public final long getAndIncrement() {
// 死循环保证并发性
while (true) {
// 获取当前值
long current = get();
// 获取下一个值
long next = current + 1;
// 利用原子CAS更新值
if (compareAndSet(current, next))
return current;
}
}
在采用CAS之前,如果在这之前有其他线程更新了这个值,那么当前值和预期值current就不相同,这将导致CAS失败,也就不会更新值为next。然后进入下一次循环,重新进行操作直到CAS操作成功。
如果在这之前没有其他线程更新这个值,那么当前值和预期值current相同,更新值为next,然后返回值current。
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
2)getAndDecrement()
获得数据的同时,进行自减操作。
3)getAndAdd(long delta)
将当前值加上delta,其实现和getAndIncrement基本一致,在一定程度上可以将后者理解为前者的特殊。
4)incrementAndGet()
获得数据的同时,进行自增操作。和getAndIncrement不同的是,其返回的值是自增之后的值。如果要用java原始的操作符解释的话,incrementAndGet可以理解为 ++i,而getAndIncrement可以立即为 i++。
5)decrementAndGet()
获得数据的同时,进行自减操作。和getAndDecrement不同的是,其返回的值是自减之后的值。如果要用java原始的操作符解释的话,decrementAndGet可以理解为 --i,而getAndDecrement可以立即为 i--。
6)addAndGet(long delta)
和getAndAdd相类似,其不同点和上面的几个不同点一样。
unsafe.compareAndSwapLong(this, valueOffSet, expect, update)如何理解呢?
第一个参数,表示该属性(需要进行CAS操作的属性)所在的对象
第二个参数,表示该属性的相对偏移量。这里的相对偏移量是指:属性所在对象的地址是A,属性的地址是B,则相对地址偏移量是B - A。
第三个参数,表示该属性预期的值。
第四个参数,表示需要更新的值。
this指向了AtomicLong的对象。而valueOffSet在类进行加载的时候就已经计算好了,因为它是一个static属性。所以对于所有的AtomicLong对象,需要更新的这个值的偏移量都是相同的。
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
上面的静态初始块,对valueOffSet进行了设置。而其调用的是unsafe.objectFiledOffSet()。
来看看下面的例子,
import java.lang.reflect.Field;
import sun.misc.Unsafe;
@SuppressWarnings("restriction")
public class InterruptTest {
private long attr1 = 100;
private long attr2 = 100;
private byte attr3 = 100;
private int attr4 = 100;
private static long valueOffset1 = 0;
private static long valueOffset2 = 0;
private static long valueOffset3 = 0;
private static long valueOffset4 = 0;
private static Unsafe unsafe = null;
static {
try {
unsafe = getUnsafe();
// 获取偏移量
valueOffset1 = unsafe.objectFieldOffset(InterruptTest.class
.getDeclaredField("attr1"));
valueOffset2 = unsafe.objectFieldOffset(InterruptTest.class
.getDeclaredField("attr2"));
valueOffset3 = unsafe.objectFieldOffset(InterruptTest.class
.getDeclaredField("attr3"));
valueOffset4 = unsafe.objectFieldOffset(InterruptTest.class
.getDeclaredField("attr4"));
} catch (Exception ex) {
}
}
@SuppressWarnings("restriction")
// 通过反射获取unsafe实例
private static Unsafe getUnsafe() {
try {
Field singleoneInstanceField = Unsafe.class
.getDeclaredField("theUnsafe");
singleoneInstanceField.setAccessible(true);
return (Unsafe) singleoneInstanceField.get(null);
} catch (IllegalArgumentException e) {
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
return unsafe;
}
public static void main(String[] args) {
InterruptTest it = new InterruptTest();
System.out.println(InterruptTest.valueOffset1);
System.out.println(InterruptTest.valueOffset2);
System.out.println(InterruptTest.valueOffset3);
System.out.println(InterruptTest.valueOffset4);
}
}
运行程序出来的结果是,
8
16
28
24
分别对应的是四个属性的偏移量。在回顾下之前的四个属性分别是long,long,byte,int,如果按照内存对其的规则,应该是8,16,24,28才对为什么最后两个反了呢?
详细的情况可以参见如下文章:
是因为发生了属性的重排。