Java并发与锁设计实现详述(6)- 聊一聊Unsafe

UnSafe这个类是什么?我相信刚接触Java的人或者没有深入研究过Java的人应该都不会知道存在这个类。这个类是干嘛的呢?在讲并发的这个系列里为什么我要提它呢?

答案很简单,那就是这个类对于并发非常重要,当然它的重要性不仅体现在对并发的支持上。之所以这篇文章说到UnSafe,是因为UnSafe类是同步队列器AbstractQueuedSynchronizer的重要组成部分,是AbstractQueuedSynchronizer中实现同步的核心,而AbstractQueuedSynchronizer又是Java同步的核心。

UnSafe类在sun.misc包下,不属于Java标准。但是很多的Java基础类库,另外包括一些使用广泛的高性能开发库底层都在使用这个类,对于提高Java的运行效率起到了很大的作用。下面是使用到UnSafe类的一些类库和框架:

(1)Netty
(2)Hazelcast
(3)Cassandra
(4)Mockito / EasyMock / JMock / PowerMock
(5)Scala Specs
(6)Spock
(7)Robolectric
(8)Grails
(9)Neo4j
(10)Spring Framework
(11)Akka
(12)Apache Kafka
(13)Apache Wink
(14)Apache Storm
(15)Apache Hadoop

(16)Apache Continuum

........这个列表很长,从中我们也看到很多应用比较广泛的如Hadoop,Kafka,Netty等,都使用到了UnSafe。

UnSafe到底是什么呢?为什么都在用它呢?

归根到底是因为UnSafe提供了硬件级别的原子操作,提高了Java对底层操作的能力。我们知道Java是类型安全的语言,Unsafe类使Java语言拥有了像C语言的指针一样的操作内存空间的能力,当然同时也带来了指针的问题。

过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。


正因为UnSafe可能会破坏Java的安全性, Oracle 铁了心毫无理由的想去掉它。下面是一个来自他们邮件列表的评论:
    "恕我直言 — sun.misc.Unsafe 必须死掉。 它是“不安全”的。它必须被废弃。请忽略一切理论上(想象中的)羁绊,从此走上正确的道路吧。"
从这可以看出这个工程师似乎是毫无根据的憎恨 Unsafe。。。

我们先不管UnSafe这些风险,毕竟现在我们并不急着自己去用它。我们还是来看下UnSafe类都能实现哪些操作功能吧,下面是UnSafe类中的一些核心方法。

1     //扩充内存  
 2     public native long reallocateMemory(long address, long bytes);  
 3       
 4     //分配内存  
 5     public native long allocateMemory(long bytes);  
 6       
 7     //释放内存  
 8     public native void freeMemory(long address);  
 9       
10     //在给定的内存块中设置值  
11     public native void setMemory(Object o, long offset, long bytes, byte value);  
12       
13     //从一个内存块拷贝到另一个内存块  
14     public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);  
15       
16     //获取值,不管java的访问限制,其他有类似的getInt,getDouble,getLong,getChar等等  
17     public native Object getObject(Object o, long offset);  
18       
19     //设置值,不管java的访问限制,其他有类似的putInt,putDouble,putLong,putChar等等  
20     public native void putObject(Object o, long offset);  
21       
22     //从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定  
23     public native long getAddress(long address);  
24       
25     //存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定  
26     public native void putAddress(long address, long x);  
27       
28     //该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的  
29     public native long staticFieldOffset(Field f);  
30       
31     //报告一个给定的字段的位置,不管这个字段是private,public还是保护类型,和staticFieldBase结合使用  
32     public native long objectFieldOffset(Field f);  
33       
34     //获取一个给定字段的位置  
35     public native Object staticFieldBase(Field f);  
36       
37     //确保给定class被初始化,这往往需要结合基类的静态域(field)  
38     public native void ensureClassInitialized(Class c);  
39       
40     //可以获取数组第一个元素的偏移地址  
41     public native int arrayBaseOffset(Class arrayClass);  
42       
43     //可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用, 可以定位数组中每个元素在内存中的位置  
44     public native int arrayIndexScale(Class arrayClass);  
45       
46     //获取本机内存的页数,这个值永远都是2的幂次方  
47     public native int pageSize();  
48       
49     //告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类  
50     public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);  
51       
52     //定义一个类,但是不让它知道类加载器和系统字典  
53     public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);  
54       
55     //锁定对象,必须是没有被锁的
56     public native void monitorEnter(Object o);  
57       
58     //解锁对象  
59     public native void monitorExit(Object o);  
60       
61     //试图锁定对象,返回true或false是否锁定成功,如果锁定,必须用monitorExit解锁  
62     public native boolean tryMonitorEnter(Object o);  
63       
64     //引发异常,没有通知  
65     public native void throwException(Throwable ee);  
66       
67     //CAS,如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。  
68     public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object x);  
69       
70     // 该方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。类似的方法有getIntVolatile,getBooleanVolatile等等  
71     public native Object getObjectVolatile(Object o, long offset);   
72       
73     //线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。  
74     public native void park(boolean isAbsolute, long time);  
75       
76     //终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,也正是使用这两个方法
77     public native void unpark(Object thread);  
78       
79     //获取系统在不同时间系统的负载情况  
80     public native int getLoadAverage(double[] loadavg, int nelems);  
81       
82     //创建一个类的实例,不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例,对于单例模式,简直是噩梦,哈哈  
83     public native Object allocateInstance(Class cls) throws InstantiationException;  

(1)内存操作

该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。

利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。

(2)非常规的对象实例化

allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

(3)操作类、对象、变量

这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

(4)数组操作

这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

(5)多线程同步

这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwapLong、compareAndSwapObject等CAS方法。但是其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。

Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

(6)挂起与恢复

这部分包括了park、unpark等方法。将一个线程进行挂起是通过park方法实现的,调用park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

(7)内存屏障

在Java8之后引入了loadFence、storeFence、fullFence等方法,用于定义内存屏障,避免代码重排序。loadFence()表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

下面给出UnSafe类中native方法的一些C代码实现,大致如下,可以简单的看下:

// natUnsafe.cc - Implementation of sun.misc.Unsafe native methods.

/** Copyright (C) 2006, 2007
   Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

#include <gcj/cni.h>
#include <gcj/field.h>
#include <gcj/javaprims.h>
#include <jvm.h>
#include <sun/misc/Unsafe.h>
#include <java/lang/System.h>
#include <java/lang/InterruptedException.h>

#include <java/lang/Thread.h>
#include <java/lang/Long.h>

#include "sysdep/locks.h"

// Use a spinlock for multi-word accesses
class spinlock
{
  static volatile obj_addr_t lock;

public:

spinlock ()
  {
    while (! compare_and_swap (&lock, 0, 1))
      _Jv_ThreadYield ();
  }
  ~spinlock ()
  {
    release_set (&lock, 0);
  }
};
  
// This is a single lock that is used for all synchronized accesses if
// the compiler can't generate inline compare-and-swap operations.  In
// most cases it'll never be used, but the i386 needs it for 64-bit
// locked accesses and so does PPC32.  It's worth building libgcj with
// target=i486 (or above) to get the inlines.
volatile obj_addr_t spinlock::lock;


static inline bool
compareAndSwap (volatile jint *addr, jint old, jint new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}
  
static inline bool
compareAndSwap (volatile jlong *addr, jlong old, jlong new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}
  
static inline bool
compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}
  

jlong
sun::misc::Unsafe::objectFieldOffset (::java::lang::reflect::Field *field)
{
  _Jv_Field *fld = _Jv_FromReflectedField (field);
  // FIXME: what if it is not an instance field?
  return fld->getOffset();
}

jint
sun::misc::Unsafe::arrayBaseOffset (jclass arrayClass)
{
  // FIXME: assert that arrayClass is array.
  jclass eltClass = arrayClass->getComponentType();
  return (jint)(jlong) _Jv_GetArrayElementFromElementType (NULL, eltClass);
}

jint
sun::misc::Unsafe::arrayIndexScale (jclass arrayClass)
{
  // FIXME: assert that arrayClass is array.
  jclass eltClass = arrayClass->getComponentType();
  if (eltClass->isPrimitive())
    return eltClass->size();
  return sizeof (void *);
}

// These methods are used when the compiler fails to generate inline
// versions of the compare-and-swap primitives.

jboolean
sun::misc::Unsafe::compareAndSwapInt (jobject obj, jlong offset,
                      jint expect, jint update)
{
  jint *addr = (jint *)((char *)obj + offset);
  return compareAndSwap (addr, expect, update);
}

jboolean
sun::misc::Unsafe::compareAndSwapLong (jobject obj, jlong offset,
                       jlong expect, jlong update)
{
  volatile jlong *addr = (jlong*)((char *) obj + offset);
  return compareAndSwap (addr, expect, update);
}

jboolean
sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
                     jobject expect, jobject update)
{
  jobject *addr = (jobject*)((char *) obj + offset);
  return compareAndSwap (addr, expect, update);
}

void
sun::misc::Unsafe::putOrderedInt (jobject obj, jlong offset, jint value)
{
  volatile jint *addr = (jint *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putOrderedLong (jobject obj, jlong offset, jlong value)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putOrderedObject (jobject obj, jlong offset, jobject value)
{
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putIntVolatile (jobject obj, jlong offset, jint value)
{
  write_barrier ();
  volatile jint *addr = (jint *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putLongVolatile (jobject obj, jlong offset, jlong value)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putObjectVolatile (jobject obj, jlong offset, jobject value)
{
  write_barrier ();
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

#if 0  // FIXME
void
sun::misc::Unsafe::putInt (jobject obj, jlong offset, jint value)
{
  jint *addr = (jint *) ((char *) obj + offset);
  *addr = value;
}
#endif

void
sun::misc::Unsafe::putLong (jobject obj, jlong offset, jlong value)
{
  jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putObject (jobject obj, jlong offset, jobject value)
{
  jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

jint
sun::misc::Unsafe::getIntVolatile (jobject obj, jlong offset)
{
  volatile jint *addr = (jint *) ((char *) obj + offset);
  jint result = *addr;
  read_barrier ();
  return result;
}

jobject
sun::misc::Unsafe::getObjectVolatile (jobject obj, jlong offset)
{
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  jobject result = *addr;
  read_barrier ();
  return result;
}

jlong
sun::misc::Unsafe::getLong (jobject obj, jlong offset)
{
  jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  return *addr;
}

jlong
sun::misc::Unsafe::getLongVolatile (jobject obj, jlong offset)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  return *addr;
}

void
sun::misc::Unsafe::unpark (::java::lang::Thread *thread)
{
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.unpark ();
}

void
sun::misc::Unsafe::park (jboolean isAbsolute, jlong time)
{
  using namespace ::java::lang;
  Thread *thread = Thread::currentThread();
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.park (isAbsolute, time);
}

虽然在前面我们已经说了,UnSafe类用起来不安全,但是如果你确定你已经深刻的理解它想通过它做一些事情,那么当然也是可以用的,在UnSafe类中,被设计成单例,并且一般代码是无法直接访问它的,只有像JDK这种调用方才会被认为是合法的调用者,因为在代码中进行了访问控制,如下所示。

public static Unsafe getUnsafe() {
	Class var0 = Reflection.getCallerClass();
	if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
		throw new SecurityException("Unsafe");
	} else {
		return theUnsafe;
	}
}

如果我们希望使用这个类,那么最简单的方法可能就是通过Java中的反射,如下所示:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
至此,关于UnSafe类的大概情况这里都描述的差不多了,感谢大家的阅读,没关注我的可以加个关注哟^_^
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值