一、摘要
?在《深入剖析Java關鍵字之volatile》的文章中,我們知道volatile關鍵字能夠解決多線程編程中的可見性辅辩,順序性這兩大問題,但是不能解決原子性的問題娃圆。那么玫锋,Java中是否有關鍵字能解決原子性問題呢?這就是我們接下來需要介紹的synchronized關鍵字讼呢,它是并發(fā)編程中的線程安全的重要保障方式之一撩鹿,它能保證可見性,順序性和原子性的問題悦屏。而且在《JMM之happens-before詳解》這篇文章中节沦,我們知道了happens-before規(guī)則中的有一條是監(jiān)視器鎖規(guī)則:對一個鎖的解鎖键思,happens-before于隨后對這個鎖的加鎖。
那么甫贯,JVM是如何實現(xiàn)synchronized這些比較厲害的特性的呢吼鳞?本文將從原理層面介紹synchronized的特性。
二叫搁、synchronized的使用方式
?synchronized關鍵字最主要的三種應用方式如下:
- 修飾實例方法赔桌,鎖是當前實例對象,進入同步代碼前要獲得當前實例的鎖渴逻;
- 修飾靜態(tài)方法疾党,鎖是當前類的Class對象,進入同步代碼前要獲得當前類對象的鎖惨奕;
-
修飾代碼塊雪位,鎖是Synchonized括號里配置的對象,指定加鎖對象墓贿,對給定對象加鎖茧泪,進入同步代碼庫前要獲得給定對象的鎖蜓氨;
?下面 依次來舉例說明synchronized關鍵字的三種使用方式聋袋;
2.1 synchronized作用于實例方法
?使用synchronized標記實例方法時(非靜態(tài)方法),只有獲得該方法對應類實例的鎖才能執(zhí)行穴吹,否則所屬線程將被阻塞幽勒,方法一旦執(zhí)行,就獨占該鎖港令,直到該方法執(zhí)行完畢將鎖釋放啥容,被阻塞的線程才能獲得鎖從而執(zhí)行。這種機制確保了同一時刻該類同一個實例
顷霹,所有聲明為synchronized的函數(shù)中只有一個方法處于可執(zhí)行狀態(tài)咪惠,從而有效避免了類實例成員變量
訪問沖突。舉例如下:
/**
* <Description> synchronized作用于類實例方法<br>
*
* @author Sunny<br>
* @version 1.0<br>
* @taskId: <br>
* @createDate 2019/02/27 14:31 <br>
* @see com.sunny.concurrent.synchronizedkey <br>
*/
public class SynchronizedMethod implements Runnable{
private static int count = 0;
public synchronized void increase() {
count++;
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
increase();
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedMethod sm = new SynchronizedMethod();
Thread t1 = new Thread(sm);
Thread t2 = new Thread(sm);
Thread t3 = new Thread(sm);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(count);
}
}
?以上程序輸出如下:
...............................
Thread-0 : 2999990
Thread-0 : 2999991
Thread-0 : 2999992
Thread-0 : 2999993
Thread-0 : 2999994
Thread-0 : 2999995
Thread-0 : 2999996
Thread-0 : 2999997
Thread-0 : 2999998
Thread-0 : 2999999
Thread-0 : 3000000
3000000
?多次執(zhí)行淋淀,不能保證每個線程每次打印的值是一樣的遥昧,但是一定能保證最后一次打印出count的值是3000000;上面的程序朵纷,我們開啟了三個線程操作同一個共享變量count炭臭,由于count++操作并不具備原子性,該操作是先讀取值袍辞,然后寫會一個新值鞋仍,相當于原來的值加上1,分兩步完成搅吁;如果其他線程在第一個線程讀取舊值和寫會新值期間讀取count的值威创,那么其他線程就會與第一個線程一起看到同一個值落午,并執(zhí)行相同值的加1操作,這也就造成了線程安全失敗肚豺,因此對于increase方法必須使用synchronized修飾來保證線程安全板甘,這個與volatile文章的分析類似。
?注意: 當synchronized修飾實例方法時详炬,同一個類的不同對象實例盐类,具有不同的鎖,同一個類的同一個實例具有相同的鎖呛谜,此時的鎖是鎖住的對象實例在跳。
當一個線程正在訪問一個對象的 synchronized 實例方法,那么其他線程不能訪問該對象的其他 synchronized 方法隐岛,畢竟一個對象實例只有一把鎖猫妙,當一個線程獲取了該對象實例的鎖之后,其他線程無法獲取該對象實例的鎖聚凹,所以無法訪問該對象實例的其他synchronized實例方法割坠,但是其他線程還是可以訪問該實例對象的其他非synchronized方法,當然如果是一個線程 A 需要訪問對象實例 object1 的 synchronized 方法 f1(當前對象鎖是object1 )妒牙,另一個線程 B 需要訪問實例對象 object12的 synchronized 方法 f2(當前對象鎖是object2)彼哼,這樣是允許的,因為兩個實例對象鎖并不同相同湘今,此時如果兩個線程操作數(shù)據(jù)并非共享的敢朱,線程安全是有保障的,遺憾的是如果兩個線程操作的是共享數(shù)據(jù)摩瞎,那么線程安全就有可能無法保證了拴签,我們把上面的程序稍微做一下改造,如下:
/**
* <Description> synchronized作用于類實例方法,因為每個線程是不同的實例對象旗们,所以synchronized并不能生效<br>
*
* @author Sunny<br>
* @version 1.0<br>
* @taskId: <br>
* @createDate 2019/02/27 14:31 <br>
* @see com.sunny.concurrent.synchronizedkey <br>
*/
public class SynchronizedMethodIncorrect implements Runnable{
private static int count = 0;
public synchronized void increase(){
count++;
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
increase();
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedMethodIncorrect smi1 = new SynchronizedMethodIncorrect();
SynchronizedMethodIncorrect smi2 = new SynchronizedMethodIncorrect();
SynchronizedMethodIncorrect smi3 = new SynchronizedMethodIncorrect();
Thread t1 = new Thread(smi1);
Thread t2 = new Thread(smi2);
Thread t3 = new Thread(smi3);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(count);
}
}
?上面程序的輸出如下:
...............................
Thread-0 : 2999987
Thread-0 : 2999988
Thread-0 : 2999989
Thread-0 : 2999990
Thread-0 : 2999991
Thread-0 : 2999992
Thread-0 : 2999993
Thread-0 : 2999994
Thread-0 : 2999995
Thread-0 : 2999996
Thread-0 : 2999997
2999997
?2999997并不是我們想要的結果蚓哩,上面的程序我們實例化了三個SynchronizedMethodIncorrect 對象,每個對象由于每個線程獲取的對象實例的鎖是不同的實例對象上渴,所以synchronized并不能生效岸梨;
2.2 synchronized作用于靜態(tài)方法
?在《Java運行時內存區(qū)域》中我們知道靜態(tài)方法存放在方法區(qū)(當然,JDK1.8之后做了調整驰贷,靜態(tài)方法應該也存儲在Java的堆中)盛嘿,而且靜態(tài)方法與類實例的方法不一樣,它是屬于類的括袒,不屬于任何一個對象實例次兆,因此,對靜態(tài)方法進行加鎖锹锰,鎖住的是當前類的Class對象芥炭。我們對2.1節(jié)中不正確的例子稍微做一些改造漓库,在increase方法上面增加static進行修飾,如下:
/**
* <Description> synchronized作用于靜態(tài)方法<br>
*
* @author Sunny<br>
* @version 1.0<br>
* @taskId: <br>
* @createDate 2019/02/27 14:31 <br>
* @see com.sunny.concurrent.synchronizedkey <br>
*/
public class SynchronizedStatic implements Runnable{
private static int count = 0;
public synchronized static void increase() {
count++;
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
increase();
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedStatic sm1 = new SynchronizedStatic();
SynchronizedStatic sm2 = new SynchronizedStatic();
SynchronizedStatic sm3 = new SynchronizedStatic();
Thread t1 = new Thread(sm1);
Thread t2 = new Thread(sm2);
Thread t3 = new Thread(sm3);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(count);
}
}
?以上程序輸出結果如下:
...............................
Thread-0 : 2999974
Thread-0 : 2999975
Thread-0 : 2999976
Thread-0 : 2999977
Thread-0 : 2999978
Thread-0 : 2999979
Thread-0 : 2999980
Thread-0 : 2999981
Thread-0 : 2999982
Thread-0 : 2999983
Thread-0 : 2999984
Thread-0 : 2999985
Thread-0 : 2999986
Thread-0 : 2999987
Thread-0 : 2999988
Thread-0 : 2999989
Thread-0 : 2999990
Thread-0 : 2999991
Thread-0 : 2999992
Thread-0 : 2999993
Thread-0 : 2999994
Thread-0 : 2999995
Thread-0 : 2999996
Thread-0 : 2999997
Thread-0 : 2999998
Thread-0 : 2999999
Thread-0 : 3000000
3000000
?不管程序執(zhí)行多少次园蝠,最后得到的結果一定是3000000渺蒿,這究竟是為什么呢?這是因為synchronized修飾靜態(tài)方法的時候彪薛,而static方法是全局的茂装,此時鎖的是SynchronizedStatic類的Class類,不管實例化多少個SynchronizedStatic的對象善延,在執(zhí)行increase方法的時候肯定是針對所有對象實例都是互斥的少态。
?需要注意的是如果一個線程A調用一個實例對象的非static synchronized方法,而線程B需要調用這個實例對象所屬類的static synchronized方法易遣,是允許的彼妻,不會發(fā)生互斥現(xiàn)象,因為訪問靜態(tài) synchronized 方法占用的鎖是當前類的class對象豆茫,而訪問非靜態(tài) synchronized 方法占用的鎖是當前實例對象鎖侨歉。
2.3 synchronized作用于代碼塊
?通常情況下,我們應用中所寫的方法會比較長揩魂,而我們僅僅需要對方法中涉及到訪問共享資源的邏輯進行同步處理幽邓,此時如果還是使用同步方法對性能影響將會比較大,這時肤京,可以使用同步代碼塊的方式颊艳,如下:
/**
* <Description> synchronized作用于代碼塊<br>
*
* @author Sunny<br>
* @version 1.0<br>
* @taskId: <br>
* @createDate 2019/02/27 14:31 <br>
* @see com.sunny.concurrent.synchronizedkey <br>
*/
public class SynchronizedCodeBlock implements Runnable{
private static int count = 0;
public void increase() {
synchronized (this) {
count++;
}
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
increase();
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCodeBlock sm = new SynchronizedCodeBlock();
Thread t1 = new Thread(sm);
Thread t2 = new Thread(sm);
Thread t3 = new Thread(sm);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(count);
}
}
?以上代碼輸出如下:
....................................
Thread-1 : 2999986
Thread-1 : 2999987
Thread-1 : 2999988
Thread-1 : 2999989
Thread-1 : 2999990
Thread-1 : 2999991
Thread-1 : 2999992
Thread-1 : 2999993
Thread-1 : 2999994
Thread-1 : 2999995
Thread-1 : 2999996
Thread-1 : 2999997
Thread-1 : 2999998
Thread-1 : 2999999
Thread-1 : 3000000
3000000
?不管代碼執(zhí)行多少次茅特,跟同步實例方法達到的效果是一致的忘分。當沒有明確的對象作為鎖,只是想讓一段代碼同步時白修,可以創(chuàng)建一個特殊的對象來充當鎖:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
public void method()
{
synchronized(lock) {
// todo 同步代碼塊
}
}
public void run() {
}
}
?說明:零長度的byte數(shù)組對象創(chuàng)建起來將比任何對象都經濟――查看編譯后的字節(jié)碼:生成零長度的byte[]對象只需3條操作碼妒峦,而Object lock = new Object()則需要7行操作碼。
?當然兵睛,如果使用這種方式肯骇,也不能new出多個不同實例對象出來啦。如果你非要想new兩個不同對象出來祖很,又想保證線程同步的話笛丙,那么synchronized后面的括號中可以填入SynchronizedCodeBlock.class,表示這個Class對象作為鎖假颇,自然就能保證線程同步胚鸯,這樣跟static的方法上面使用synchronized修飾的效果類似。如下:
public void increase() {
synchronized (SynchronizedCodeBlock.class) {
count++;
}
}
?通過演示3種不同鎖的使用笨鸡,讓大家對synchronized有了初步的認識姜钳。當一個線程試圖訪問帶有synchronized修飾的同步代碼塊或者方法時坦冠,必須要先獲得鎖。當方法執(zhí)行完畢退出以后或者出現(xiàn)異常的情況下會自動釋放鎖哥桥。如果大家認真看了上面的三個案例辙浑,那么應該知道鎖的范圍控制是由對象的作用域決定的。對象的作用域越大拟糕,那么鎖的范圍也就越大判呕,因此我們可以得出一個初步的猜想,synchronized和對象有非常大的關系送滞。那么佛玄,接下來就去剖析一下鎖的原理。
三累澡、synchronized實現(xiàn)原理
?問題:當一個線程嘗試訪問synchronized修飾的代碼塊時梦抢,它首先要獲得鎖,那么這個鎖到底存在哪里呢愧哟?
3.1 對象在內存中的布局
?synchronized實現(xiàn)的鎖是存儲在Java對象頭里奥吩,什么是對象頭呢?在Hotspot虛擬機中蕊梧,對象在內存中的存儲布局霞赫,可以分為三個區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)肥矢、對齊填充(Padding)
?當我們在Java代碼中端衰,使用new創(chuàng)建一個對象實例的時候,(Hotspot虛擬機)JVM層面實際上會創(chuàng)建一個instanceOopDesc對象甘改。
Hotspot虛擬機采用OOP-Klass二分模型來描述Java對象實例旅东,其中OOP(Ordinary Object Point)指的是普通對象指針,它用來表示對象的實例信息十艾,看起來像個指針實際上是藏在指針里的對象抵代;而 Klass 則包含 元數(shù)據(jù)和方法信息,用來描述 Java 類忘嫉。Hotspot采用instanceOopDesc和arrayOopDesc來描述對象頭荤牍,arrayOopDesc對象用來描述數(shù)組類型。
那么為何要設計這樣一個一分為二的對象模型呢庆冕?這是因為 HotSopt JVM 的設計者不想讓每個對象中都含有一個 vtable(虛函數(shù)表)康吵,所以就把對象模型拆成 klass 和 oop,其中 oop 中不含有任何虛函數(shù)访递,而 klass 就含有虛函數(shù)表晦嵌,可以進行 method dispatch。這個模型其實是參照的 Strongtalk VM 底層的對象模型。
?instanceOopDesc的定義在Hotspot源碼中的instanceOop.hpp文件中耍铜,另外邑闺,arrayOopDesc的定義對應arrayOop.hpp
// An instanceOop is an instance of a Java Class
// Evaluating "new HashTable()" will create an instanceOop.
class instanceOopDesc : public oopDesc {
public:
// aligned header size.
static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }
// If compressed, the offset of the fields of the instance may not be aligned.
static int base_offset_in_bytes() {
// offset computation code breaks if UseCompressedClassPointers
// only is true
return (UseCompressedOops && UseCompressedClassPointers) ?
klass_gap_offset_in_bytes() :
sizeof(instanceOopDesc);
}
static bool contains_field_offset(int offset, int nonstatic_field_size) {
int base_in_bytes = base_offset_in_bytes();
return (offset >= base_in_bytes &&
(offset-base_in_bytes) < nonstatic_field_size * heapOopSize);
}
};
#endif // SHARE_VM_OOPS_INSTANCEOOP_HPP
?從instanceOopDesc代碼中可以看到 instanceOopDesc繼承自oopDesc,oopDesc的定義載Hotspot源碼中的 oop.hpp文件中
class oopDesc {
friend class VMStructs;
friend class JVMCIVMStructs;
private:
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
public:
inline markOop mark() const;
inline markOop mark_raw() const;
inline markOop* mark_addr_raw() const;
inline void set_mark(volatile markOop m);
inline void set_mark_raw(volatile markOop m);
static inline void set_mark_raw(HeapWord* mem, markOop m);
inline void release_set_mark(markOop m);
inline markOop cas_set_mark(markOop new_mark, markOop old_mark);
inline markOop cas_set_mark_raw(markOop new_mark, markOop old_mark, atomic_memory_order order = memory_order_conservative);
// Used only to re-initialize the mark word (e.g., of promoted
// objects during a GC) -- requires a valid klass pointer
inline void init_mark();
inline void init_mark_raw();
inline Klass* klass() const;
inline Klass* klass_or_null() const volatile;
inline Klass* klass_or_null_acquire() const volatile;
static inline Klass** klass_addr(HeapWord* mem);
static inline narrowKlass* compressed_klass_addr(HeapWord* mem);
inline Klass** klass_addr();
inline narrowKlass* compressed_klass_addr();
inline void set_klass(Klass* k);
static inline void release_set_klass(HeapWord* mem, Klass* klass);
// For klass field compression
inline int klass_gap() const;
inline void set_klass_gap(int z);
static inline void set_klass_gap(HeapWord* mem, int z);
// For when the klass pointer is being used as a linked list "next" field.
inline void set_klass_to_list_ptr(oop k);
inline oop list_ptr_from_klass();
// size of object header, aligned to platform wordSize
static int header_size() { return sizeof(oopDesc)/HeapWordSize; }
// Returns whether this is an instance of k or an instance of a subclass of k
inline bool is_a(Klass* k) const;
// Returns the actual oop size of the object
inline int size();
// Sometimes (for complicated concurrency-related reasons), it is useful
// to be able to figure out the size of an object knowing its klass.
inline int size_given_klass(Klass* klass);
// type test operations (inlined in oop.inline.hpp)
inline bool is_instance() const;
inline bool is_array() const;
inline bool is_objArray() const;
inline bool is_typeArray() const;
// type test operations that don't require inclusion of oop.inline.hpp.
bool is_instance_noinline() const;
bool is_array_noinline() const;
bool is_objArray_noinline() const;
bool is_typeArray_noinline() const;
protected:
inline oop as_oop() const { return const_cast<oopDesc*>(this); }
public:
// field addresses in oop
inline void* field_addr(int offset) const;
inline void* field_addr_raw(int offset) const;
// Need this as public for garbage collection.
template <class T> inline T* obj_field_addr_raw(int offset) const;
template <typename T> inline size_t field_offset(T* p) const;
// Standard compare function returns negative value if o1 < o2
// 0 if o1 == o2
// positive value if o1 > o2
inline static int compare(oop o1, oop o2) {
void* o1_addr = (void*)o1;
void* o2_addr = (void*)o2;
if (o1_addr < o2_addr) {
return -1;
} else if (o1_addr > o2_addr) {
return 1;
} else {
return 0;
}
}
inline static bool equals(oop o1, oop o2) { return Access<>::equals(o1, o2); }
// Access to fields in a instanceOop through these methods.
template <DecoratorSet decorator>
oop obj_field_access(int offset) const;
oop obj_field(int offset) const;
void obj_field_put(int offset, oop value);
void obj_field_put_raw(int offset, oop value);
void obj_field_put_volatile(int offset, oop value);
Metadata* metadata_field(int offset) const;
void metadata_field_put(int offset, Metadata* value);
Metadata* metadata_field_acquire(int offset) const;
void release_metadata_field_put(int offset, Metadata* value);
jbyte byte_field(int offset) const;
void byte_field_put(int offset, jbyte contents);
jchar char_field(int offset) const;
void char_field_put(int offset, jchar contents);
jboolean bool_field(int offset) const;
void bool_field_put(int offset, jboolean contents);
jint int_field(int offset) const;
void int_field_put(int offset, jint contents);
jshort short_field(int offset) const;
void short_field_put(int offset, jshort contents);
jlong long_field(int offset) const;
void long_field_put(int offset, jlong contents);
jfloat float_field(int offset) const;
void float_field_put(int offset, jfloat contents);
jdouble double_field(int offset) const;
void double_field_put(int offset, jdouble contents);
address address_field(int offset) const;
void address_field_put(int offset, address contents);
oop obj_field_acquire(int offset) const;
void release_obj_field_put(int offset, oop value);
jbyte byte_field_acquire(int offset) const;
void release_byte_field_put(int offset, jbyte contents);
jchar char_field_acquire(int offset) const;
void release_char_field_put(int offset, jchar contents);
jboolean bool_field_acquire(int offset) const;
void release_bool_field_put(int offset, jboolean contents);
jint int_field_acquire(int offset) const;
void release_int_field_put(int offset, jint contents);
jshort short_field_acquire(int offset) const;
void release_short_field_put(int offset, jshort contents);
jlong long_field_acquire(int offset) const;
void release_long_field_put(int offset, jlong contents);
jfloat float_field_acquire(int offset) const;
void release_float_field_put(int offset, jfloat contents);
jdouble double_field_acquire(int offset) const;
void release_double_field_put(int offset, jdouble contents);
address address_field_acquire(int offset) const;
void release_address_field_put(int offset, address contents);
// printing functions for VM debugging
void print_on(outputStream* st) const; // First level print
void print_value_on(outputStream* st) const; // Second level print.
void print_address_on(outputStream* st) const; // Address printing
// printing on default output stream
void print();
void print_value();
void print_address();
// return the print strings
char* print_string();
char* print_value_string();
// verification operations
void verify_on(outputStream* st);
void verify();
// locking operations
inline bool is_locked() const;
inline bool is_unlocked() const;
inline bool has_bias_pattern() const;
inline bool has_bias_pattern_raw() const;
// asserts and guarantees
static bool is_oop(oop obj, bool ignore_mark_word = false);
static bool is_oop_or_null(oop obj, bool ignore_mark_word = false);
#ifndef PRODUCT
inline bool is_unlocked_oop() const;
#endif
// garbage collection
inline bool is_gc_marked() const;
// Forward pointer operations for scavenge
inline bool is_forwarded() const;
inline void forward_to(oop p);
inline bool cas_forward_to(oop p, markOop compare, atomic_memory_order order = memory_order_conservative);
// Like "forward_to", but inserts the forwarding pointer atomically.
// Exactly one thread succeeds in inserting the forwarding pointer, and
// this call returns "NULL" for that thread; any other thread has the
// value of the forwarding pointer returned and does not modify "this".
inline oop forward_to_atomic(oop p, atomic_memory_order order = memory_order_conservative);
inline oop forwardee() const;
inline oop forwardee_acquire() const;
// Age of object during scavenge
inline uint age() const;
inline void incr_age();
// mark-sweep support
void follow_body(int begin, int end);
// Garbage Collection support
#if INCLUDE_PARALLELGC
// Parallel Compact
inline void pc_follow_contents(ParCompactionManager* cm);
inline void pc_update_contents(ParCompactionManager* cm);
// Parallel Scavenge
inline void ps_push_contents(PSPromotionManager* pm);
#endif
template <typename OopClosureType>
inline void oop_iterate(OopClosureType* cl);
template <typename OopClosureType>
inline void oop_iterate(OopClosureType* cl, MemRegion mr);
template <typename OopClosureType>
inline int oop_iterate_size(OopClosureType* cl);
template <typename OopClosureType>
inline int oop_iterate_size(OopClosureType* cl, MemRegion mr);
template <typename OopClosureType>
inline void oop_iterate_backwards(OopClosureType* cl);
inline static bool is_instanceof_or_null(oop obj, Klass* klass);
// identity hash; returns the identity hash key (computes it if necessary)
// NOTE with the introduction of UseBiasedLocking that identity_hash() might reach a
// safepoint if called on a biased object. Calling code must be aware of that.
inline intptr_t identity_hash();
intptr_t slow_identity_hash();
// Alternate hashing code if string table is rehashed
unsigned int new_hash(juint seed);
// marks are forwarded to stack when object is locked
inline bool has_displaced_mark_raw() const;
inline markOop displaced_mark_raw() const;
inline void set_displaced_mark_raw(markOop m);
static bool has_klass_gap();
// for code generation
static int mark_offset_in_bytes() { return offset_of(oopDesc, _mark); }
static int klass_offset_in_bytes() { return offset_of(oopDesc, _metadata._klass); }
static int klass_gap_offset_in_bytes() {
assert(has_klass_gap(), "only applicable to compressed klass pointers");
return klass_offset_in_bytes() + sizeof(narrowKlass);
}
};
#endif // SHARE_VM_OOPS_OOP_HPP
?在普通實例對象中棕兼,oopDesc的定義包含兩個成員陡舅,分別是_mark和_metadata。_mark表示對象標記伴挚、屬于markOop類型靶衍,也就是接下來要講解的Mark Word,它記錄了對象和鎖的有關信息茎芋。_metadata表示類元信息颅眶,類元信息存儲的是對象指向它的類元數(shù)據(jù)(Klass)的首地址,其中Klass表示普通指針田弥、_compressed_klass表示壓縮類指針趾徽。
3.2 Mark Word
?在前面我們提到過现恼,普通對象的對象頭由兩部分組成篮奄,分別是markOop以及類元信息兔综,markOop官方稱為Mark Word 在Hotspot中,markOop的定義在 markOop.hpp文件中只泼,代碼如下:
typedef class markOopDesc* markOop;
?而markOopDesc的定義如下:
class markOopDesc: public oopDesc {
private:
// Conversion
uintptr_t value() const { return (uintptr_t) this; }
public:
// Constants
enum { age_bits = 4,
lock_bits = 2,
biased_lock_bits = 1,
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits,
cms_bits = LP64_ONLY(1) NOT_LP64(0),
epoch_bits = 2
};
// The biased locking code currently requires that the age bits be
// contiguous to the lock bits.
enum { lock_shift = 0,
biased_lock_shift = lock_bits,
age_shift = lock_bits + biased_lock_bits,
cms_shift = age_shift + age_bits,
hash_shift = cms_shift + cms_bits,
epoch_shift = hash_shift
};
enum { lock_mask = right_n_bits(lock_bits),
lock_mask_in_place = lock_mask << lock_shift,
biased_lock_mask = right_n_bits(lock_bits + biased_lock_bits),
biased_lock_mask_in_place= biased_lock_mask << lock_shift,
biased_lock_bit_in_place = 1 << biased_lock_shift,
age_mask = right_n_bits(age_bits),
age_mask_in_place = age_mask << age_shift,
epoch_mask = right_n_bits(epoch_bits),
epoch_mask_in_place = epoch_mask << epoch_shift,
cms_mask = right_n_bits(cms_bits),
cms_mask_in_place = cms_mask << cms_shift
#ifndef _WIN64
,hash_mask = right_n_bits(hash_bits),
hash_mask_in_place = (address_word)hash_mask << hash_shift
#endif
};
// Alignment of JavaThread pointers encoded in object header required by biased locking
enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)
};
#ifdef _WIN64
// These values are too big for Win64
const static uintptr_t hash_mask = right_n_bits(hash_bits);
const static uintptr_t hash_mask_in_place =
(address_word)hash_mask << hash_shift;
#endif
enum { locked_value = 0,
unlocked_value = 1,
monitor_value = 2,
marked_value = 3,
biased_lock_pattern = 5
};
enum { no_hash = 0 }; // no hash value assigned
enum { no_hash_in_place = (address_word)no_hash << hash_shift,
no_lock_in_place = unlocked_value
};
enum { max_age = age_mask };
enum { max_bias_epoch = epoch_mask };
// Biased Locking accessors.
// These must be checked by all code which calls into the
// ObjectSynchronizer and other code. The biasing is not understood
// by the lower-level CAS-based locking code, although the runtime
// fixes up biased locks to be compatible with it when a bias is
// revoked.
bool has_bias_pattern() const {
return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}
JavaThread* biased_locker() const {
assert(has_bias_pattern(), "should not call this otherwise");
return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
}
// Indicates that the mark has the bias bit set but that it has not
// yet been biased toward a particular thread
bool is_biased_anonymously() const {
return (has_bias_pattern() && (biased_locker() == NULL));
}
// Indicates epoch in which this bias was acquired. If the epoch
// changes due to too many bias revocations occurring, the biases
// from the previous epochs are all considered invalid.
int bias_epoch() const {
assert(has_bias_pattern(), "should not call this otherwise");
return (mask_bits(value(), epoch_mask_in_place) >> epoch_shift);
}
markOop set_bias_epoch(int epoch) {
assert(has_bias_pattern(), "should not call this otherwise");
assert((epoch & (~epoch_mask)) == 0, "epoch overflow");
return markOop(mask_bits(value(), ~epoch_mask_in_place) | (epoch << epoch_shift));
}
markOop incr_bias_epoch() {
return set_bias_epoch((1 + bias_epoch()) & epoch_mask);
}
// Prototype mark for initialization
static markOop biased_locking_prototype() {
return markOop( biased_lock_pattern );
}
// lock accessors (note that these assume lock_shift == 0)
bool is_locked() const {
return (mask_bits(value(), lock_mask_in_place) != unlocked_value);
}
bool is_unlocked() const {
return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value);
}
bool is_marked() const {
return (mask_bits(value(), lock_mask_in_place) == marked_value);
}
bool is_neutral() const { return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value); }
// Special temporary state of the markOop while being inflated.
// Code that looks at mark outside a lock need to take this into account.
bool is_being_inflated() const { return (value() == 0); }
// Distinguished markword value - used when inflating over
// an existing stacklock. 0 indicates the markword is "BUSY".
// Lockword mutators that use a LD...CAS idiom should always
// check for and avoid overwriting a 0 value installed by some
// other thread. (They should spin or block instead. The 0 value
// is transient and *should* be short-lived).
static markOop INFLATING() { return (markOop) 0; } // inflate-in-progress
// Should this header be preserved during GC?
inline bool must_be_preserved(oop obj_containing_mark) const;
inline bool must_be_preserved_with_bias(oop obj_containing_mark) const;
// Should this header (including its age bits) be preserved in the
// case of a promotion failure during scavenge?
// Note that we special case this situation. We want to avoid
// calling BiasedLocking::preserve_marks()/restore_marks() (which
// decrease the number of mark words that need to be preserved
// during GC) during each scavenge. During scavenges in which there
// is no promotion failure, we actually don't need to call the above
// routines at all, since we don't mutate and re-initialize the
// marks of promoted objects using init_mark(). However, during
// scavenges which result in promotion failure, we do re-initialize
// the mark words of objects, meaning that we should have called
// these mark word preservation routines. Currently there's no good
// place in which to call them in any of the scavengers (although
// guarded by appropriate locks we could make one), but the
// observation is that promotion failures are quite rare and
// reducing the number of mark words preserved during them isn't a
// high priority.
inline bool must_be_preserved_for_promotion_failure(oop obj_containing_mark) const;
inline bool must_be_preserved_with_bias_for_promotion_failure(oop obj_containing_mark) const;
// Should this header be preserved during a scavenge where CMS is
// the old generation?
// (This is basically the same body as must_be_preserved_for_promotion_failure(),
// but takes the Klass* as argument instead)
inline bool must_be_preserved_for_cms_scavenge(Klass* klass_of_obj_containing_mark) const;
inline bool must_be_preserved_with_bias_for_cms_scavenge(Klass* klass_of_obj_containing_mark) const;
// WARNING: The following routines are used EXCLUSIVELY by
// synchronization functions. They are not really gc safe.
// They must get updated if markOop layout get changed.
markOop set_unlocked() const {
return markOop(value() | unlocked_value);
}
bool has_locker() const {
return ((value() & lock_mask_in_place) == locked_value);
}
BasicLock* locker() const {
assert(has_locker(), "check");
return (BasicLock*) value();
}
bool has_monitor() const {
return ((value() & monitor_value) != 0);
}
ObjectMonitor* monitor() const {
assert(has_monitor(), "check");
// Use xor instead of &~ to provide one extra tag-bit check.
return (ObjectMonitor*) (value() ^ monitor_value);
}
bool has_displaced_mark_helper() const {
return ((value() & unlocked_value) == 0);
}
markOop displaced_mark_helper() const {
assert(has_displaced_mark_helper(), "check");
intptr_t ptr = (value() & ~monitor_value);
return *(markOop*)ptr;
}
void set_displaced_mark_helper(markOop m) const {
assert(has_displaced_mark_helper(), "check");
intptr_t ptr = (value() & ~monitor_value);
*(markOop*)ptr = m;
}
markOop copy_set_hash(intptr_t hash) const {
intptr_t tmp = value() & (~hash_mask_in_place);
tmp |= ((hash & hash_mask) << hash_shift);
return (markOop)tmp;
}
// it is only used to be stored into BasicLock as the
// indicator that the lock is using heavyweight monitor
static markOop unused_mark() {
return (markOop) marked_value;
}
// the following two functions create the markOop to be
// stored into object header, it encodes monitor info
static markOop encode(BasicLock* lock) {
return (markOop) lock;
}
static markOop encode(ObjectMonitor* monitor) {
intptr_t tmp = (intptr_t) monitor;
return (markOop) (tmp | monitor_value);
}
static markOop encode(JavaThread* thread, uint age, int bias_epoch) {
intptr_t tmp = (intptr_t) thread;
assert(UseBiasedLocking && ((tmp & (epoch_mask_in_place | age_mask_in_place | biased_lock_mask_in_place)) == 0), "misaligned JavaThread pointer");
assert(age <= max_age, "age too large");
assert(bias_epoch <= max_bias_epoch, "bias epoch too large");
return (markOop) (tmp | (bias_epoch << epoch_shift) | (age << age_shift) | biased_lock_pattern);
}
// used to encode pointers during GC
markOop clear_lock_bits() { return markOop(value() & ~lock_mask_in_place); }
// age operations
markOop set_marked() { return markOop((value() & ~lock_mask_in_place) | marked_value); }
markOop set_unmarked() { return markOop((value() & ~lock_mask_in_place) | unlocked_value); }
uint age() const { return mask_bits(value() >> age_shift, age_mask); }
markOop set_age(uint v) const {
assert((v & ~age_mask) == 0, "shouldn't overflow age field");
return markOop((value() & ~age_mask_in_place) | (((uintptr_t)v & age_mask) << age_shift));
}
markOop incr_age() const { return age() == max_age ? markOop(this) : set_age(age() + 1); }
// hash operations
intptr_t hash() const {
return mask_bits(value() >> hash_shift, hash_mask);
}
bool has_no_hash() const {
return hash() == no_hash;
}
// Prototype mark for initialization
static markOop prototype() {
return markOop( no_hash_in_place | no_lock_in_place );
}
// Helper function for restoration of unmarked mark oops during GC
static inline markOop prototype_for_object(oop obj);
// Debugging
void print_on(outputStream* st) const;
// Prepare address of oop for placement into mark
inline static markOop encode_pointer_as_mark(void* p) { return markOop(p)->set_marked(); }
// Recover address of oop from encoded form used in mark
inline void* decode_pointer() { if (UseBiasedLocking && has_bias_pattern()) return NULL; return clear_lock_bits(); }
// These markOops indicate cms free chunk blocks and not objects.
// In 64 bit, the markOop is set to distinguish them from oops.
// These are defined in 32 bit mode for vmStructs.
const static uintptr_t cms_free_chunk_pattern = 0x1;
// Constants for the size field.
enum { size_shift = cms_shift + cms_bits,
size_bits = 35 // need for compressed oops 32G
};
// These values are too big for Win64
const static uintptr_t size_mask = LP64_ONLY(right_n_bits(size_bits))
NOT_LP64(0);
const static uintptr_t size_mask_in_place =
(address_word)size_mask << size_shift;
#ifdef _LP64
static markOop cms_free_prototype() {
return markOop(((intptr_t)prototype() & ~cms_mask_in_place) |
((cms_free_chunk_pattern & cms_mask) << cms_shift));
}
uintptr_t cms_encoding() const {
return mask_bits(value() >> cms_shift, cms_mask);
}
bool is_cms_free_chunk() const {
return is_neutral() &&
(cms_encoding() & cms_free_chunk_pattern) == cms_free_chunk_pattern;
}
size_t get_size() const { return (size_t)(value() >> size_shift); }
static markOop set_size_and_free(size_t size) {
assert((size & ~size_mask) == 0, "shouldn't overflow size field");
return markOop(((intptr_t)cms_free_prototype() & ~size_mask_in_place) |
(((intptr_t)size & size_mask) << size_shift));
}
#endif // _LP64
};
#endif // SHARE_VM_OOPS_MARKOOP_HPP
?Mark Word記錄了對象和鎖有關的信息剖笙,當某個對象被synchronized關鍵字當成同步鎖時,那么圍繞這個鎖的一系列操作都和Mark Word有關系请唱。Mark Word在32位虛擬機的長度是32bit弥咪、在64位虛擬機的長度是64bit。
32位JVM的Mark Word的默認存儲結構(無鎖狀態(tài))
在運行期間十绑,Mark Word里存儲的數(shù)據(jù)會隨著鎖標志位的變化而變化(32位)
64位JVM的Mark Word的默認存儲結構(對于32位無鎖狀態(tài)聚至,有25bit沒有使用)
?Mark Word里面存儲的數(shù)據(jù)會隨著鎖標志位的變化而變化,Mark Word可能變化為存儲以下5種情況:
?鎖標志位的表示意義:
- 鎖標識lock=00標識輕量級鎖
- 鎖標識lock=10標識重量級鎖
- 偏向鎖標識biased_lock=1標識偏向鎖
- 偏向鎖標識biased_lock=0標識無鎖狀態(tài)
?綜上所述孽惰,synchronized(lock)中的lock可以用Java中任何一個對象來表示晚岭,而鎖標識的存儲實際上就是在lock這個對象中的對象頭內。
?其實前面只提到了鎖標志位的存儲勋功,但是為什么任意一個Java對象都能成為鎖對象呢?
?Java中的每個對象都派生自Object類库说,而每個Java Object在JVM內部都有一個native的C++對象oop/oopDesc進行對應狂鞋。其次,線程在獲取鎖的時候潜的,實際上就是獲得一個監(jiān)視器對象(monitor)骚揍,monitor可以認為是一個同步對象,所有的Java對象是天生攜帶monitor。在hotspot源碼的markOop.hpp文件中信不,可以看到下面這段代碼嘲叔。
ObjectMonitor* monitor() const {
assert(has_monitor(), "check");
// Use xor instead of &~ to provide one extra tag-bit check.
return (ObjectMonitor*) (value() ^ monitor_value);
}
?多個線程訪問同步代碼塊時,相當于去爭搶對象監(jiān)視器修改對象中的鎖標識,上面的代碼中ObjectMonitor這個對象和線程爭搶鎖的邏輯有密切的關系抽活。
3.3 鎖優(yōu)化與升級
?前面提到了鎖的幾個概念硫戈,偏向鎖、輕量級鎖下硕、重量級鎖丁逝。在JDK1.6之前,synchronized是一個重量級鎖梭姓,性能比較差霜幼。在JDK1.6之后,為了減少獲得鎖和釋放鎖帶來的性能消耗誉尖,synchronized進行了優(yōu)化罪既,引入了偏向鎖
和輕量級
鎖的概念。所以從JDK1.6開始铡恕,鎖一共會有四種狀態(tài)萝衩,鎖的狀態(tài)根據(jù)競爭激烈程度從低到高分別是:無鎖狀態(tài) -> 偏向鎖狀態(tài) -> 輕量級鎖狀態(tài) -> 重量級鎖狀態(tài)。這幾個狀態(tài)會隨著鎖競爭的情況逐步升級没咙。為了提高獲得鎖和釋放鎖的效率猩谊,鎖可以升級但是不能降級。鎖可以升級但是不能降級意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖祭刚。
3.3.1 偏向鎖
?Hotspot的作者經過研究發(fā)現(xiàn)牌捷,大多數(shù)情況下,鎖不僅不存在多線程競爭涡驮,而且總是由同一線程多次獲得暗甥,為了讓線程獲得鎖的代價更低而引入了偏向鎖。偏向鎖的意思是如果一個線程獲得了一個偏向鎖捉捅,如果在接下來的一段時間中沒有其他線程來競爭鎖撤防,那么持有偏向鎖的線程再次進入或者退出同一個同步代碼塊,不需要再次進行搶占鎖和釋放鎖的操作棒口。偏向鎖可以通過 -XX:+UseBiasedLocking開啟或者關閉
偏向鎖的獲取
?偏向鎖的獲取過程非常簡單寄月,當一個線程訪問同步塊獲取鎖時,會在對象頭和棧幀的鎖記錄里存儲偏向鎖的線程ID无牵,表示哪個線程獲得了偏向鎖漾肮,結合前面的Mark Word來分析一下偏向鎖的獲取邏輯:
- 首先獲取目標對象的Mark Word,根據(jù)鎖的標識為epoch去判斷當前是否處于可偏向的狀態(tài)茎毁;
- 如果為可偏向狀態(tài)克懊,則通過CAS操作將自己的線程ID寫入到Mark Word,如果CAS操作成功,則表示當前線程成功獲取到偏向鎖谭溉,繼續(xù)執(zhí)行同步代碼塊墙懂;
- 如果是已偏向狀態(tài),先檢測Mark Word中存儲的threadID和當前訪問線程的threadID是否相等扮念,如果相等损搬,表示當前線程已經獲得了偏向鎖,則不需要在獲得鎖直接執(zhí)行同步代碼扔亥;如果不相等场躯,則證明當前鎖偏向于其他線程,需要撤銷偏向鎖旅挤。
偏向鎖的撤銷
?偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖
的機制踢关,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖粘茄。偏向鎖的撤銷签舞,需要等到全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)
。
- 首先會暫停擁有偏向鎖的線程并檢查該線程是否存活:
1.1 如果線程非活動狀態(tài)柒瓣,則將對象頭設置為無鎖狀態(tài)(其他線程會重新獲取該偏向鎖)
1.2 如果線程是活動狀態(tài)儒搭,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向對象的鎖記錄芙贫,并將對棧中的鎖記錄和對象頭的MarkWord進行重置:
- 要么重新偏向于其他線程(即將偏向鎖交給其他線程搂鲫,相當于當前線程"被"釋放了鎖)
- 要么恢復到無鎖或者標記鎖對象不適合作為偏向鎖(此時鎖會被升級為輕量級鎖)
- 最后喚醒暫停的線程,被阻塞在安全點的線程繼續(xù)往下執(zhí)行同步代碼塊
偏向鎖的關閉
- 偏向鎖在JDK1.6以上默認開啟磺平,開啟后程序啟動幾秒后才會被激活
- 有必要可以使用JVM參數(shù)來關閉延遲 -XX:BiasedLockingStartupDelay = 0
- 如果確定鎖通常處于競爭狀態(tài)魂仍,則可通過JVM參數(shù) -XX:-UseBiasedLocking=false 關閉偏向鎖,那么默認會進入輕量級鎖
偏向鎖的獲取流程圖
偏向鎖的注意事項
- 優(yōu)勢:偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令拣挪,其余時刻不需要CAS指令(相比其他鎖)
- 隱患:由于一旦出現(xiàn)多線程競爭的情況就必須撤銷偏向鎖擦酌,所以偏向鎖的撤銷操作的性能損耗必須小于節(jié)省下來的CAS原子指令的性能消耗(這個通常只能通過大量壓測才可知)
- 對比:輕量級鎖是為了在線程交替執(zhí)行同步塊時提高性能,而偏向鎖則是在只有一個線程執(zhí)行同步塊時進一步提高性能
3.3.2 輕量級鎖
?當存在超過一個線程在競爭同一個同步代碼塊時菠劝,會發(fā)生偏向鎖的撤銷赊舶,而撤銷偏向鎖的時候就是當鎖對象不適合作為偏向鎖的時候會被升級為輕量級鎖,這個就是我們接下來需要介紹的輕量級鎖赶诊。
輕量級鎖加鎖
- 線程在執(zhí)行同步代碼塊之前笼平,JVM會先在當前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中(Displaced Mark Word - 即被取代的Mark Word)做一份拷貝
- 拷貝成功后甫何,線程嘗試使用CAS將對象頭的Mark Word替換為指向鎖記錄的指針(將對象頭的Mark Word更新為指向鎖記錄的指針出吹,并將鎖記錄里的Owner指針指向Object Mark Word)
- 如果更新成功,當前線程獲得輕量級鎖辙喂,繼續(xù)執(zhí)行同步方法
- 如果更新失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋(CAS)來獲取鎖巍耗,當自旋超過指定次數(shù)(可以自定義)時仍然無法獲得鎖秋麸,此時鎖會膨脹升級為重量級鎖
輕量級鎖解鎖
- 嘗試CAS操作將鎖記錄中的Mark Word替換會對象頭中
- 如果成功,表示沒有競爭發(fā)生
- 如果失敗炬太,表示當前鎖存在競爭灸蟆,鎖會膨脹成重量級鎖
一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態(tài)亲族。當鎖處于重量級鎖狀態(tài)炒考,其他線程嘗試獲取鎖時,都會被阻塞霎迫,也就是 BLOCKED狀態(tài)斋枢。當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒之后的線程會進行新一輪的競爭
輕量級鎖獲取鎖的流程
輕量級鎖的注意事項
-
隱患
: 對于輕量級鎖有個使用前提是”沒有多線程競爭環(huán)境”知给,一旦越過這個前提瓤帚,除了互斥開銷外,還會增加額外的CAS操作的開銷涩赢,在多線程競爭環(huán)境下戈次,輕量級鎖甚至比重量級鎖還要慢
3.3.3 重量級鎖
?重量級鎖依賴對象內部的monitor(可以研究下Monitor Object機制)鎖來實現(xiàn),而monitor又依賴操作系統(tǒng)的MutexLock(互斥鎖)
?大家如果對MutexLock有興趣筒扒,可以抽時間去了解怯邪,假設Mutex變量的值為1,表示互斥鎖空閑花墩,這個時候某個線程調用lock可以獲得鎖悬秉,而Mutex的值為0表示互斥鎖已經被其他線程獲得,其他線程調用lock只能掛起等待观游。
為什么重量級鎖的開銷比較大呢搂捧?
?原因是當系統(tǒng)檢查到是重量級鎖之后,會把等待想要獲取鎖的線程阻塞懂缕,被阻塞的線程不會消耗CPU允跑,但是阻塞或者喚醒一個線程,都需要通過操作系統(tǒng)來實現(xiàn)搪柑,也就是相當于從用戶態(tài)轉化到內核態(tài)聋丝,而轉化狀態(tài)是需要消耗時間的。
四工碾、總結
?synchronized是通過monitor監(jiān)視器鎖來實現(xiàn)可見性弱睦、順序性以及原子性、而monitor是依賴于操作系統(tǒng)的mutex(互斥鎖渊额,每個對象都對應于一個可稱為" 互斥鎖" 的標記况木,這個標記用來保證在任一時刻垒拢,只能有一個線程訪問該對象)。這里簡單介紹下mutex的操作步驟:
申請mutex
如果成功火惊,則持有該mutex
如果失敗求类,則進行spin自旋. spin的過程就是在線等待mutex, 不斷發(fā)起mutex gets, 直到獲得mutex或者達到spin_count限制為止
依據(jù)工作模式的不同選擇yiled還是sleep
若達到sleep限制或者被主動喚醒或者完成yield, 則重復1)~4)步,直到獲得為止
參考引用
https://www.processon.com/view/5c25db87e4b016324f447c95
方騰飛《Java并發(fā)編程的藝術》