Java 9的發(fā)布的新特性除了最主要的模塊化之外浅侨,在API方面也為開發(fā)者們帶來了很多有用的特性技俐,本篇我們來探討一下java 9提供的新的API-VarHandle 對 memory order 的支持蕴侣,及其在JUC同步類中的應用涡驮。在開始本篇之前固耘,你需要對JMM(Java 內存模型)有一定的認知眷射。
VarHandle 的必要性
隨著Java中的并發(fā)和并行編程的不斷擴大,我們經常會需要對某個類的字段進行原子或有序操作雕沿,但是 JVM 對Java開發(fā)者所開放的權限非常有限。例如:如果要原子性地增加某個字段的值猴仑,到目前為止我們可以使用下面三種方式:
- 使用
AtomicInteger
來達到這種效果审轮,這種間接管理方式增加了空間開銷,還會導致額外的并發(fā)問題辽俗; - 使用原子性的
FieldUpdaters
疾渣,由于利用了反射機制,操作開銷也會更大崖飘; - 使用
sun.misc.Unsafe
提供的JVM內置函數(shù)API榴捡,雖然這種方式比較快,但它會損害安全性和可移植性朱浴,當然在實際開發(fā)中也很少會這么做吊圾。
在 VarHandle 出現(xiàn)之前达椰,這些潛在的問題會隨著原子API的不斷擴大而越來越遭。VarHandle 的出現(xiàn)替代了java.util.concurrent.atomic
和sun.misc.Unsafe
的部分操作项乒。并且提供了一系列標準的內存屏障操作啰劲,用于更加細粒度的控制內存排序。在安全性檀何、可用性蝇裤、性能上都要優(yōu)于現(xiàn)有的API。VarHandle 可以與任何字段频鉴、數(shù)組元素或靜態(tài)變量關聯(lián)栓辜,支持在不同訪問模型下對這些類型變量的訪問,包括簡單的 read/write 訪問垛孔,volatile 類型的 read/write 訪問啃憎,和 CAS(compare-and-swap)等。
創(chuàng)建VarHandle
VarHandle通過MethodHandles
的lookup()
方法創(chuàng)建似炎,下面演示了非靜態(tài)變量和數(shù)組的獲取方式:
public class VarhandleFoo {
private Point[] points;
private static final VarHandle QA;//for arrays
private static final VarHandle X;//for Variables
static {
try {
QA = MethodHandles.arrayElementVarHandle(Point[].class);
X = MethodHandles.lookup().
findVarHandle(Point.class, "x", int.class); //or
//X = MethodHandles.lookup().in(Point.class).findVarHandle(Point.class, "x", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
class Point {
volatile int x;
// ...
}
}
Lookup
Lookup
是MethodHandles
的內部類辛萍,位于java.lang.invoke
包中,它是一個用于創(chuàng)建方法和變量句柄的工廠羡藐。與我們熟知的核心反射API不同的是贩毕,核心反射API在每一次方法被調用時都會進行訪問檢查,而通過Lookup
創(chuàng)建的方法句柄的訪問檢查是在它被創(chuàng)建時進行的仆嗦。通過Lookup
提供的工廠方法辉阶,我們可以訪問對象的任何方法、構造函數(shù)和參數(shù)變量瘩扼。由工廠方法創(chuàng)建的每個方法句柄都等同于方法的字節(jié)碼行為(bytecode behavior)谆甜,也就是說,JVM調用方法句柄與執(zhí)行和方法句柄相關的字節(jié)碼行為一致集绰。(有關Lookup的更詳細介紹规辱,請參閱官方API-MethodHandles.Lookup)
Lookup
在Java 9中對變量訪問也添加了相應的工廠方法,用于生成變量句柄-VarHandle:
-
findVarHandle
:用于創(chuàng)建對象中非靜態(tài)字段的VarHandle
栽燕。接收參數(shù)有三個罕袋,第一個為接收者的class對象,第二個是字段名稱碍岔,第三個是字段類型浴讯。 -
findStaticVarHandle
:用于創(chuàng)建對象中靜態(tài)字段的VarHandle
,接收參數(shù)與findVarHandle
一致蔼啦。 -
unreflectVarHandle
:通過反射字段Field
創(chuàng)建VarHandle
榆纽。
獲取VarHandle
后,接下來就是對變量的訪問,下面列舉了幾種簡單的訪問形式:
//plain read
int x = (int) X.get(this);
Point p = (Point) QA.get(points,10);
//plain write
X.set(this,1);
QA.set(points,10,new Point());
//CAS
X.compareAndSet(this,0,1);
QA.compareAndSet(points,10,p,new Point());
//Numeric Atomic Update
X.getAndAdd(this,10);
VarHandle
中的每個方法都被稱為 access mode method奈籽,接收的參數(shù)都是一個協(xié)調表達式饥侵,該表達式精確地指示了要訪問變量的對象,后續(xù)的調用參數(shù)表示當前訪問模式的值唠摹。例如爆捞,CAS方法需要兩個后續(xù)參數(shù):預期值和新值。
需要注意的是勾拉,access mode將覆蓋在變量聲明時指定的任何內存排序效果煮甥。 例如,一個VarHandle使用 get 模式訪問一個字段時藕赞,即使這個字段已經被聲明為volatile成肘,也會把這個字段當做方法指定的訪問模式進行訪問。因此使用時要非常小心斧蜕。
內存屏障
VarHandle
除了支持在各種訪問模式下訪問變量之外双霍,還提供了一組內存屏障方法,為內存排序提供更細粒度的控制批销。主要有以下幾個方法:
public static void fullFence() {
UNSAFE.fullFence();
}
public static void acquireFence() {
UNSAFE.loadFence();
}
public static void releaseFence() {
UNSAFE.storeFence();
}
public static void loadLoadFence() {
UNSAFE.loadLoadFence();
}
public static void storeStoreFence() {
UNSAFE.storeStoreFence();
}
本質上來看洒闸,這些內存屏障都是通過Unsafe
類的fullFence
、loadFence
和storeFence
來實現(xiàn)均芽,關于Unsafe
丘逸,大家可以參考我的另外一篇文章:JUC源碼分析—CAS和Unsafe。
VarHandle的應用
自VarHandle
出世后掀宋,JUC中多數(shù)同步類中都使用VarHandle
替代了Unsafe
深纲,這里我們拿AQS舉例說明,直接看代碼:
// VarHandle mechanics
private static final VarHandle STATE;
private static final VarHandle HEAD;
private static final VarHandle TAIL;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class);
TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
// Reduce the risk of rare disastrous classloading in first call to
// LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
Class<?> ensureLoaded = LockSupport.class;
}
/**
* 初始化queue
*/
private final void initializeSyncQueue() {
Node h;
if (HEAD.compareAndSet(this, null, (h = new Node())))
tail = h;
}
AQS中對變量state, head, tail
的訪問都改為了VarHandle
方式劲妙。例如湃鹊,如果要用CAS方式修改head
節(jié)點,只需要調用VarHandle
的compareAndSet
即可(HEAD.compareAndSet(this, null, new Node())
)镣奋。
目前币呵,在java.util.concurrent
包中對變量的訪問基本上都由Unsafe
改為了VarHandle
,有關VarHandle的更多詳細使用方式唆途,請參考JUC源碼富雅。