1. Java鎖的重偏向機制
1.1 偏向鎖機制
我們知道,當我們使用synchronized關鍵字的時候,一個對象a只被一個對象訪問的時候,對對象加的鎖偏向鎖,如果之後出現第二個線程訪問a的時候(這裏只考慮線程交替執行的情況,不存在競爭),不管線程1是已死亡還是運行狀態,此時鎖都會升級為輕量鎖,並且鎖升級過程不可逆。
1.2 批量重偏向
但是如果有很多對象,這些對象同屬於一個類(假設是類A)被線程1訪問並加偏向鎖,之後線上2來訪問這些對象(不考慮競爭情況),在通過CAS操作把這些鎖升級為輕量鎖,會是一個很耗時的操作。
JVM對此作了優化:
*當對象數量超過某個閾值時(默認20, jvm啓動時加參數-XX:+PrintFlagsFinal可以打印這個閾值* ),Java會對超過的對象作批量重偏向線程2,此時前20個對象是輕量鎖,
後面的對象都是偏向鎖,且偏向線程2。**
2.代碼
public static void main(String[] args) throws Exception {
List<A> list=new ArrayList<>();
//生成40個A的實例
for (int i = 0; i < 40; i++) {
list.add(new A());
}
//t1線程對前20個對象加鎖
Thread t1= new Thread(){
public void run() {
for (int i = 0; i <20; i++) {
A a=list.get(i);
synchronized (a) {
if(i==1 || i==19) {
System.out.println("線程t1 lock " + i + “號對象” );
//打印對象頭
out.println(ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
}
}
}
};
t1.start();
t1.join();//通過join是t1結束後再啓動t2,避免競爭
new Thread().start();
//t2線程再次對前20個對象加鎖
Thread t2= new Thread(){
public void run() {
for (int i = 0; i < 20; i++) {
A a=list.get(i);
synchronized (a) {
if(i==1 || i==19) {//i<19的時候輕量鎖, i>=19的時候是偏向鎖
System.out.println("線程t2 lock " + i );
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
}
};
t2.start();
t2.join();
}
3.執行結果
線程t1 lock i=1
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c0 0c 1d (00000101 11000000 00001100 00011101) (487374853)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
線程t1 lock i=19
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c0 0c 1d (00000101 11000000 00001100 00011101) (487374853)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
線程t2 lock i=1
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f5 f7 1e (01000000 11110101 11110111 00011110) (519566656)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
線程t2 lock i=19
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 d1 1b 1d (00000101 11010001 00011011 00011101) (488362245)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
結果分析
從上面的t1線程的結果全是偏向鎖。
t2線程中,前19個對象是輕量鎖,第20個對象(i=19)開始是偏向鎖。
下面打印出來的對象頭第一行第一個字節的後三位如下:
| 倒數第三位(偏向標識) | 最後兩位(lock標識) | 鎖 |
|---|---|---|
| 0 | 01 | 無鎖 |
| 1 | 01 | 偏向鎖 |
| 0 | 00 | 輕量鎖 |
t2線程i=1的對象頭第一行第一個字節是: 01000000 最後三位000,是輕量鎖。
t2線程i=19的對象頭第一行第一個字節是:00000101 最後三位101,是偏向鎖。
4、總結
JVM對synchronized鎖做了很多優化,這也是為什麼synchronized鎖的性能和ReentrantLock相比較的原因,在這些優化之後,synchronized和ReentrantLock性能上不相上下了,使用上更加方便,是最好的選擇