博客 / 詳情

返回

Java鎖優化之批量重偏向

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性能上不相上下了,使用上更加方便,是最好的選擇

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.