1. 程式人生 > >Java併發與鎖設計實現詳述(6)- 聊一聊Unsafe

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類的大概情況這裡都描述的差不多了,感謝大家的閱讀,沒關注我的可以加個關注喲^_^