Java AtomicLong

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

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

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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值