Java AtomicLong

来源:互联网 发布:淘宝手机使用模板 编辑:程序博客网 时间:2024/06/03 01:10

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。

这里采用了compareAndSet函数保证了原子性和并发性,来看看这个函数的实现,

    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);}}

运行程序出来的结果是,
8162824
分别对应的是四个属性的偏移量。在回顾下之前的四个属性分别是long,long,byte,int,如果按照内存对其的规则,应该是8,16,24,28才对为什么最后两个反了呢?

详细的情况可以参见如下文章:

http://www.codeinstructions.com/2008/12/sizeof-for-java.html

是因为发生了属性的重排。

0 0