Java并发包Atomic 及Unsafe

 

  Java.util.concurrent.atomic下,有一堆AtomicXxxx对象。对于这些AtomicXxxx对象的使用,大家都知道,操作是线程安全的。不过大家有没有关心在多线程应用中,这些Atomic操作的线程安全是怎么实现的,究竟虚拟机是怎么来保证这些Atomic操作的呢?我们以AtomicLong为例,来分析Atomic操作究竟是怎么实现的。

大家可以仔细看看AtomicLong的源码,在AtomicLong中,定义了一个变量:

Private volatile long value
 

Volatile这个关键字的使用一直是爱恨交加,用得好的话事半功倍,如果用的不恰当则事倍功半。在此处,该value值是直接使用,不依赖外部变量的改变;并且该Value值直接放入Unsafe存储,直接和Memory打交道;和Memory打交道还有个能保证原子性的CAS的操作,所以volatile在此处使用刚好合适,volatile的其它使用场景请自行查找吧。这个value就是保存存入的值。另外还有这三个变量定义:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static final Boolean VM_SUPPORT_LONG_CAS;
 

Ok,这四个变量定义就能够保证对long类型的原子操作。其中VM_SUPPORT_LONG_CAS变量实际上是表示该虚拟机是否支持CAS操作。Unsafe变量和valueOffset变量才是真正进行atomic操作的关键,操作的都是value值。下面我们来仔细分析下unsafevalueOffset



Unsafe类归属于sun.misc包,是JDK内部类,是限制使用的。我们需要将compiler选项限制级别降低,才能正常引用,想看Unsafe的源码请戳这里valueOffset表示的是操作的这个数据类型的偏移量,实际上就是数据类型所占的字节数2倍。

我们看到源码后,想获得Unsafe的实例,于是很高兴的写下了下面这个代码:

Unsafe unsafe = Unsafe.getUnsafe();
 

Sorry,你高兴的太早了,这个方法的使用是受限的,会抛出SecurityException,根本拿不到unsafe的实例。那怎么办呢?没关系,我们有万能的Reflection操作来实现。


正确的获得unsafe实例的代码应该如下:

 

 

       Unsafe unsafe = null;
        try {
            Field field;
            field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
 

 

看到unsafe的定义是private static final,这样的定义也能通过反射得到,那么变量作用范围声明关键字privateprotected还有什么用呢?以后大家如果想拿到某个被限制获得的类的话,要优先考虑reflection哦。其实我想说的是范围声明的关键字设定在reflection面前形同虚设,不知道这是不是符合Java的初衷,一家之言,仅供参考。


点开Unsafe源码,看到通篇native方法,仅有的几个非native方法,你再多看下还是native调用。这个native声明大家都不陌生,我们先看下的使用

        long value = 20121221L;
        byte size = 1;
        long address = unsafe.allocateMemory(size);
        System.out.println("Address:" + address);
        unsafe.putLong(address, value);
        long readValue = unsafe.getAddress(address);
        System.out.println("Value:" + readValue);
 

这个native方法的实现依赖于JVM,实现方法无从得知,但是从方法名字上可以看出来,这个unsafe实际上操作的是JVM的内存分配allocate、管理putget、拷贝copy、重分配reallocate和释放free。这个分配的内存没有进行初始化,并且能够自动类型对齐。很明显,这个操作和和Javanew Object操作完全不一样,反而和C++new操作一致;这块内存需要用户自己申请,自己进行操作。不同的是C++申请的是系统内存,而unsafe申请的是JVM控制的空间,那么这个native block是申请在stack上还是申请在heap上呢?大家可以思考一下。


看到这里,大家应该对于atomic原子操作的底层有了解了吧。其实Java也是可以通过APIC++一样直接操作虚拟机内存的。如果以后谁在告诉你Java不能直接操作内存的话,你可以以此为例给Ta一个耳光;更准确的应该说他们没有全部说对,应该说Java可以直接操作虚拟机控制的内存。


好吧,通过Unsafe操作确实能够保证赋值、读值的原子操作,等等,这就完了?No,麻烦大家看下AtomicXxxx的 getAndSet、 getAndIncrement、 getAndDecrement、  getAndAdd等方法操作,发现并不是直接委托给unsafe处理,而是添加了一些额外的操作。这些额外的操作究竟是什么意思?用的还是循环处理操作直到成功为止。这个操作有什么玄机,或者说其它的理论支持呢?且看我们下次分析。


猜你喜欢

转载自isilic.iteye.com/blog/1720630