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标识)
001无锁
101偏向锁
000轻量锁

t2线程i=1的对象头第一行第一个字节是: 01000000 最后三位000,是轻量锁。

t2线程i=19的对象头第一行第一个字节是:00000101 最后三位101,是偏向锁。

4、总结

JVM对synchronized锁做了很多优化,这也是为什么synchronized锁的性能和ReentrantLock相比较的原因,在这些优化之后,synchronized和ReentrantLock性能上不相上下了,使用上更加方便,是最好的选择


杜若
70 声望3 粉丝

引用和评论

0 条评论