目錄
- 1.小故事
- 2.輕量級鎖
- 3.鎖膨脹
- 4.自旋優化
- 5.偏向鎖
- 5.1.概述
- 5.2.偏向鎖狀態
- 5.3.偏向鎖撤銷
- 5.3.1.呼叫物件hashCode
- 5.3.2.其它執行緒使用物件
- 5.3.3.呼叫wait/notify
- 5.4.批量重偏向
- 5.5.批量撤銷
- 6.其它優化
- 6.1. 減少上鎖時間
- 6.2.減少鎖的粒度
- 6.3.鎖粗化
- 6.4.鎖消除
- 6.5. 讀寫分離
關于synchronized底層作業原理在上一節介紹過,本節在上節的基礎上講解synchronized優化
1.小故事
synchronized作業方式是讓每個物件關聯一個monitor,monitor鎖是由作業系統提供的,要使用它成本較高,如果是每次進入synchronized的話需要獲得一個monitor鎖,那么就需要很大的開銷,從Java6開始對synchronized獲取鎖的方式進行了優化,除了可以使用輕量級鎖,還可以使用偏向級鎖來進行優化,
故事角色
-
老王 - JVM
-
小南 - 執行緒
-
小女 - 執行緒
-
房間 - 物件
-
房間門上 - 防盜鎖 - Monitor
-
房間門上 - 小南書包 - 輕量級鎖
-
房間門上 - 刻上小南大名 - 偏向鎖
-
批量重刻名 - 一個類的偏向鎖撤銷到達 20 閾值
-
不能刻名字 - 批量撤銷該類物件的偏向鎖,設定該類不可偏向
小南要使用房間保證計算不被其它人干擾(原子性),最初,他用的是防盜鎖,當背景關系切換時,鎖住門,這樣,即使他離開了,別人也進不了門,他的作業就是安全的,
但是,很多情況下沒人跟他來競爭房間的使用權,小女是要用房間,但使用的時間上是錯開的,小南白天用,小女晚上用,每次上鎖太麻煩了,有沒有更簡單的辦法呢?
小南和小女商量了一下,約定不鎖門了,而是誰用房間,誰把自己的書包掛在門口,但他們的書包樣式都一樣,因此每次進門前得翻翻書包,看課本是誰的,如果是自己的,那么就可以進門,這樣省的上鎖解鎖了,萬一書包不是自己的,那么就在門外等,并通知對方下次用鎖門的方式,
后來,小女回老家了,很長一段時間都不會用這個房間,小南每次還是掛書包,翻書包,雖然比鎖門省事了,但仍然覺得麻煩,
于是,小南干脆在門上刻上了自己的名字:【小南專屬房間,其它人勿用】,下次來用房間時,只要名字還在,那么說明沒人打擾,還是可以安全地使用房間,如果這期間有其它人要用這個房間,那么由使用者將小南刻的名字擦掉,升級為掛書包的方式,(偏向鎖,偏向某個物件)
同學們都放假回老家了,小南就膨脹了,在 20 個房間刻上了自己的名字,想進哪個進哪個,后來他自己放假回老家了,這時小女回來了(她也要用這些房間),結果就是得一個個地擦掉小南刻的名字,升級為掛書包的方式,老王覺得這成本有點高,提出了一種批量重刻名的方法,他讓小女不用掛書包了,可以直接在門上刻上自己的名字
后來,刻名的現象越來越頻繁,老王受不了了:算了,這些房間都不能刻名了,只能掛書包.
2.輕量級鎖
輕量級鎖使用場景:如果一個物件雖然有多執行緒訪問,但多執行緒訪問的時間是錯開的(也就是沒有競爭),那么可以使用輕量級鎖來優化.如果發生了競爭,那么鎖會升級為重量級鎖,
輕量級鎖對使用者是透明的,語法依然是synchronized
假設有兩個方法同步塊,利用同一個物件加鎖
static final Object obj = new Object();
public static void method1(){
synchronized(obj){
// 同步塊A
method2();
}
}
public static void method2(){
synchronized(obj){
// 同步塊 B
}
}
創建鎖記錄(Lock record)物件,每個執行緒具備的堆疊幀都會包含一個所記錄的結構,內部可以存盤鎖定物件的Mark Vord;

Lock Record中,Object reference記錄鎖物件的參考地址,lock record 地址 00記錄物件的mark word,
Object中:
Mark Word物件頭:hashcode 哈希碼 Age:分代年齡 狀態位
Klass Word: 型別指標:
Object body:類的成員變數;
Thread-0堆疊幀中
Lock Record中,Object reference記錄鎖物件的參考地址,lock record 地址 00記錄物件的mark word
其中,markword結構如下:

讓鎖記錄中Object reference指向鎖物件,并嘗試用cas替換Object的mark word,將mark word的值存入所記錄,
將鎖記錄資料與物件頭資料 進行交換,表示加鎖;
00表示的是一種輕量級鎖狀態,01表示無鎖狀態;見上圖;

如果cas替換成功,物件頭中存盤了鎖記錄地址和狀態00,表示由該執行緒給物件加鎖,如下圖:

如果cas失敗,有兩種情況
- 如果是其他執行緒已經持有了該Object的輕量級鎖,這時表明有競爭,進入鎖膨脹程序(下一節);
- 如果是自己執行了synchronized鎖重入,那么再添加一條Lock Record作為重入的計數
也即第一次對它加了鎖,第二次又對它加了鎖,如上面的method1里面對obj加了鎖,呼叫method2方法,又對obj加了鎖,此時又產生了一個堆疊幀,這個堆疊幀里面又進入了synchronized,也會創建鎖記錄,objectreferce指向剛開要鎖定的物件,然后進行cas交換的操作,不過這次交換失敗了, 因為剛才已經由自己把后兩位改成了00,但是它知道這是當前執行緒中另外一條鎖記錄,這種情況叫做synchronized鎖沖入,也就是自己執行緒又一次給同一個物件加鎖了,這種情況再加一個lock record,只不過資料部分存入的是鎖重入的計數,

當退出synchronized代碼塊(解鎖時)如果有取值為null的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一

當退出synchronized代碼塊(解鎖時)鎖記錄的值不為null,這時使用cas將Mark Word的值恢復給物件頭
成功,則解鎖成功
失敗,說明輕量級鎖進行了鎖膨脹已經升級為重量級鎖,進入重量級鎖解鎖流程,
3.鎖膨脹
如果在嘗試加輕量級鎖的程序中,CAS操作無法成功,這時一種情況就是有其他執行緒為此物件加上了輕量級鎖(有競爭),這時候需要進行鎖膨脹,將輕量級鎖變為重量級鎖,
static Object obj = new Object();
public static void method1(){
synchronized(obj){
// 同步塊
}
}
當Thread-1進行輕量級加鎖時,Thread-0已經對該物件加了輕量級鎖

這時Thread-1加輕量級鎖失敗(cas操作失敗),進入鎖膨脹流程
- 即為Object物件申請Monitor鎖,讓Object指向重量級鎖地址
- 然后自己進入Monitor的EntryList BLOCKED

重量級后面兩位是00
當Thread-0退出同步塊解鎖時,使用cas將Mark Word的值恢復給物件頭,失敗,這時候會進入重量級解鎖流程,即按照Monitor地址找到Monitor物件,設定Owner為null,喚醒EntryList中BLOCKED執行緒;
4.自旋優化
重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前執行緒是自旋成功(即這時候將鎖執行緒已經退出了同步塊,釋放了鎖),這時當前執行緒就可以避免阻塞.
因為阻塞要法生產執行緒背景關系切換,是比較耗時的,用自旋的方式就可以避免執行緒背景關系切換的發生,
自旋適用于多核CPU的場景下,單核CPU沒有意義
自旋成功的情況
| 執行緒1(CPU1上) | 物件Mark | 執行緒2(CPU2) |
|---|---|---|
| - | 10(重量級鎖) | - |
| 訪問同步塊,獲取Monitor | 10(重量級鎖)重量鎖指標 | - |
| 成功(加鎖) | 10(重量級鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 訪問同步塊,獲取Monitor |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 自旋重試 |
| 執行完畢 | 10(重量級鎖)重量鎖指標 | 自旋嘗試 |
| 成功解鎖 | 01無鎖 | 自旋嘗試 |
| - | 10(重量鎖)重量鎖指標 | 成功加鎖 |
| - | 10(重量鎖)重量鎖指標 | 執行同步塊 |
自旋失敗的情況
| 執行緒1(CPU1上) | 物件Mark | 執行緒2(CPU2) |
|---|---|---|
| - | 10(重量級鎖) | - |
| 訪問同步塊,獲取Monitor | 10(重量級鎖)重量鎖指標 | - |
| 成功(加鎖) | 10(重量級鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 訪問同步塊,獲取Monitor |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量級鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 阻塞 |
- 在Java6之后自旋鎖是自適應的,比如物件剛剛的一次自旋操作成功過,那么認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能,
- 自旋會占用CPU時間,單核 CPU自旋就是浪費,多核CPU自旋才能發揮優勢*
- Java7之后不能控制是否開啟自旋功能
5.偏向鎖
5.1.概述
輕量級鎖在沒有競爭時,(就自己這個執行緒),每次重入仍然執行CAS操作.
Java6中引入了偏向鎖來做進一步優化;只有第一次使用CAS將執行緒ID設定到物件的Mark Word頭,之后發現這個執行緒ID是自己的就表示沒有競爭,不用重新CAS,以后只要不發生競爭,這個物件就歸該執行緒所有
例如:
static final Object obj = new Object();
public static void m1(){
synchronized(obj){
// 同步塊 A
m2();
}
}
public static void m2(){
//同一個執行緒再次對同一個物件加鎖,此時m1還未釋放鎖
synchronized(obj){
// 同步塊 B
m3();
}
}
public static void m3(){
//同一個執行緒再次對同一個物件加鎖,此時m1,m2還未釋放鎖
synchronized(obj){
// 同步塊 C
}
}

優化,類似把名字刻在房間門口

即這個物件就偏向這個執行緒,
5.2.偏向鎖狀態

如圖,biased_lock表示是否啟用了偏向鎖,如果是0,沒有啟用偏向鎖,1就表明偏向狀態,
如果是偏向狀態,存盤的就是執行緒id,epoch在后面會用到,
一個物件創建時:
如果開啟了偏向鎖(默認開啟),那么物件創建后,markword值為0x05即最后3位101,這時它的Thread,epoch,age都為0
偏向鎖默認是延遲的,不會在程式啟動時立即生效,如果想避免延遲,可以加VM引數-xx:BiaseLockingStartupDelay=0來禁用延遲
如果沒有開啟偏向鎖,那么物件創建后,markword值為0x01即最后3位位001,這時它的hashcode,age,都為0,第一次用到hashcode時才會賦值
偏向鎖的延遲啟動時間
偏向鎖默認是在JVM啟動4s后再初始化偏向鎖,可用如下引數修改啟動時間,設為0則表示立即啟用,之所以這么設計是因為JVM啟動的時候,如果立即啟動偏向,有可能會因為執行緒競爭太激烈導致產生太多安全點掛起,
-XX:BiasedLockingStartupDelay=0
如果多執行緒環境下, 資源經常需要競爭使用,那么這個時候就不適合用偏向鎖了,在測驗代碼運行時在添加VM引數-XX:UserBiasedLocking禁用偏向鎖,
5.3.偏向鎖撤銷
5.3.1.呼叫物件hashCode
呼叫了物件的hashCode,但偏向鎖的物件MarkWord中存盤的是執行緒id,如果呼叫hashCode會導致偏向鎖被撤銷
- 輕量級鎖會在鎖記錄中記錄hashCode
- 重量級鎖會在Monitor中記錄hashCode
在呼叫hashCode后使用偏向鎖,記得去掉-XX:UseBiasedLocking
5.3.2.其它執行緒使用物件
當有其他執行緒使用偏向鎖物件時,會將偏向鎖升級為輕量級鎖
注意:我們要讓兩個執行緒交錯開,必須是t1執行緒先解鎖,t2再去加鎖,如果有競爭那么就是重量級鎖,輕量級鎖和偏向鎖的前提是執行緒訪問物件時必須是錯開的,我們可以使用wait/notify來實作這樣的效果,
private static void test2() throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
// 如果不用 wait/notify 使用 join 必須打開下面的注釋
// 因為:t1 執行緒不能結束,否則底層執行緒可能被 jvm 重用作為 t2 執行緒,底層執行緒 id 是一樣的
/*try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}*/
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}, "t2");
t2.start();
}
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
5.3.3.呼叫wait/notify
public static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}
5.4.批量重偏向
如果物件雖然被多個執行緒訪問,但沒有競爭,這時偏向了執行緒 T1 的物件仍有機會重新偏向 T2,重偏向會重置物件的 Thread ID
當撤銷偏向鎖閾值超過 20 次后,jvm 會這樣覺得,我是不是偏向錯了呢,于是會在給這些物件加鎖時重新偏向至加鎖執行緒
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
5.5.批量撤銷
當撤銷偏向鎖閾值超過 40 次后,jvm 會這樣覺得,自己確實偏向錯了,根本就不該偏向,于是整個類的所有物件都會變為不可偏向的,新建的物件也是不可偏向的,
static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
6.其它優化
6.1. 減少上鎖時間
同步代碼塊中盡量短.,
上鎖時間短,競爭機會少,如果是交錯運行下,此時可以用輕量級鎖來優化,如果上鎖時間長,交錯機會就增加了,輕量級鎖就會升級為重量級鎖,所以盡量讓synchronized代碼塊盡可能短,
6.2.減少鎖的粒度
將一個鎖拆分為多個鎖提高并發度
- ConcurrentHashMap(在陣列的鏈表頭上進行加鎖,如果hashtable就鎖住了整個的hashtable,如果我只鎖住了鏈表頭,加鎖的粒度就變小了,每次只鎖住了一個鏈表,其它鏈表讀寫操作不會受到任何影響)
- LongAdder分為base和cells兩部分,沒有并發爭用的時候或者是cells陣列正在初始化時候,會使用CAS來累加值到base,有并發爭用,會初始化 cells 陣列,陣列有多少個 cell,就允 許有多少執行緒并行修改,后將陣列中每個 cell 累加,再加上 base 就是終的值
- LinkedBlockingQueue 入隊和出隊使用不同的鎖,相對于LinkedBlockingArray只有一個鎖效率要高
6.3.鎖粗化
多次回圈進入同步塊不如同步塊內多次回圈 另外 JVM 可能會做如下優化,把多次 append 的加鎖操作 粗化為一次(因為都是對同一個物件加鎖,沒必要重入多次)
new StringBuffer().append("a").append("b").append("c");
6.4.鎖消除
JVM 會進行代碼的逃逸分析,例如某個加鎖物件是方法內區域變數,不會被其它執行緒所訪問到,這時候 就會被即時編譯器忽略掉所有同步操作
6.5. 讀寫分離
CopyOnWriteArrayList ConyOnWriteSet
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/257053.html
標籤:其他
