新闻资讯

新闻资讯 行业动态

Java并发之synchronized关键字深度解析(三)

编辑:006     时间:2020-02-15

前言

    本篇主要介绍一下synchronized的批量重偏向和批量撤销机制,属于深水区,大家提前备好氧气瓶。

   上一篇说完synchronized锁的膨胀过程,下面我们再延伸一下synchronized锁的两种特殊处理,一种是锁的批量重偏向,一种是锁的批量撤销。JVM中有两个参数,BiasedLockingBulkRebiasThreshold和BiasedLockingBulkRevokeThreshold,前者默认阈值为20,控制批量重偏向;后者默认阈值为40,控制批量撤销。下面我们分别看一下它们是如何发挥作用的。

一、批量重偏向

    synchronized关键字不支持单个的重偏向,但是可以批量进行。所谓批量重偏向,是指不存在锁竞争的条件下,如果同一个类有50个对象偏向线程t1,而线程t2又分别对这50个对象进行循环加锁,此时t2加锁的前19个对象会膨胀为轻量锁,等到第20个对象时,JVM会预测这个类后面的所有对象都要偏向t2,所以再加锁时,就不会执行锁膨胀了,而是重偏向到线程t2。

    只说理论可能大家没什么感知,下面用测试代码来演示一下:

复制代码
 1 package com.mybokeyuan.lockDemo;
 2 
 3 import org.openjdk.jol.info.ClassLayout;
 4 
 5 import java.util.ArrayList;
 6 import java.util.List;
 7 public class BulkLockClientTest {
 8     static List<User> userList = new ArrayList<>();
 9     public static void main(String[] args) throws Exception {
10         // 先睡眠5秒,保证开启偏向锁
11         try {
12             Thread.sleep(5000);
13         } catch (InterruptedException e) { // -XX:-UseBiasedLocking
14             e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0
15  }
16         for (int i = 0; i < 50; i++) {
17             userList.add(new User()); // 开启了偏向锁,所以新建的对象都是初始化的偏向锁状态
18  }
19         System.out.println("userList.get(0):");
20         System.out.println(ClassLayout.parseInstance(userList.get(0)).toPrintable());
21 
22         Thread t1 = new Thread(() -> {
23             // 循环加锁,加锁后list中的user都是t1线程的偏向锁
24             for (User user : userList) {
25                 synchronized (user) {
26  }
27  }
28             try {
29                 Thread.sleep(1000000);
30             } catch (InterruptedException e) {
31  e.printStackTrace();
32  }
33  });
34  t1.start();
35         // 保证t1中的for循环结束
36         Thread.sleep(3000);
37         System.out.println("after t1");
38         // 此时打印出来的对象头,全都是偏向t1线程的偏向锁
39         System.out.println(ClassLayout.parseInstance(userList.get(0)).toPrintable());
40 
41         Thread t2 = new Thread(() -> {
42             for (int i = 0; i < userList.size(); i++) {
43                 // 在t2线程中循环加锁,加到第20个user的时候,会触发批量重偏向,第20个以及后面的对象变为偏向t2线程的偏向锁
44                 // 而之前的19个对象仍然为轻量级锁
45                 synchronized (userList.get(i)) {
46                     if (i == 0 || i == 18 || i == 19 || i == 20) {
47                         System.out.println("running t2: " + (i+1));
48  System.out.println(ClassLayout.parseInstance(userList.get(i)).toPrintable());
49  }
50  }
51  }
52 
53  });
54  t2.start();
55  }
56 }
复制代码

结果为:

复制代码
userList.get(0):
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

after t1
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 d8 95 1a (00000101 11011000 10010101 00011010) (446027781)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t2: 1 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           d0 f0 2a 1b (11010000 11110000 00101010 00011011) (455798992)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t2: 19 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           d0 f0 2a 1b (11010000 11110000 00101010 00011011) (455798992)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t2: 20 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 e1 95 1a (00000101 11100001 10010101 00011010) (446030085)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t2: 21 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 e1 95 1a (00000101 11100001 10010101 00011010) (446030085)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
复制代码

    可以看到,在t2线程的同步块中,第1到第19个对象仍然是轻量级锁,而到第二十个对象以及后面,全部变为了偏向t2线程的偏向锁。这就是批量重偏向机制。批量重偏向是针对类对象-Class对象设置的,如果同一个类有连续20个对象都被另一个线程同步持有,那么JVM就会放弃膨胀为轻量级锁的处理方式,而是将后面出现的此对象实例重偏向到这个新的线程。

 

二、批量撤销

    假设有80个A类的对象实例,开始时全部偏向线程t1,然后t2线程对第1-40个对象又进行了加锁处理,此时根据批量重定向机制,1-19个对象先是会膨胀为轻量级锁,退出同步块后变为无锁;而第20-40个对象会因为触发批量重定向,锁状态变为偏向t2线程的偏向锁。这时t3线程来了,它对第21-43个对象进行加锁处理(注意t3线程的前20个对象不能跟t2线程的前20个对象重合),这时由于t2线程撤销偏向锁撤销了19次(JVM会按20次计算),t3线程撤销偏向锁撤销了19次(JVM会按20次计算),总共撤销的次数达到了40的阈值,此时JVM会判定为这个A类的对象有问题(不断的切换偏向线程会降低执行效率),从第21-43,都会变为轻量级锁,不再进行重偏向操作,而且会对这个A类的对象关闭偏向的设置,即往后再newA类的对象时,不会进入偏向锁状态,只能走无锁-轻量级锁0重量级锁的膨胀过程。所以批量撤销全称应该为:批量撤销偏向锁

   下面通过模拟代码来展现批量撤销的过程:

复制代码
 1 package com.mybokeyuan.lockDemo;
 2 
 3 import org.openjdk.jol.info.ClassLayout;
 4 
 5 import java.util.ArrayList;
 6 import java.util.List;
 7 // -XX:+PrintFlagsFinal
 8 public class BulkLockClientTest {
 9     static List<User> userList = new ArrayList<>();
10     public static void main(String[] args) throws Exception {
11         // 先睡眠5秒,保证开启偏向锁
12         try {
13             Thread.sleep(5000);
14         } catch (InterruptedException e) { // -XX:-UseBiasedLocking
15             e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0
16  }
17         for (int i = 0; i < 80; i++) {
18             userList.add(new User()); // 开启了偏向锁,所以新建的对象都是初始化的偏向锁状态
19  }
20         System.out.println("userList.get(0):");
21         System.out.println(ClassLayout.parseInstance(userList.get(0)).toPrintable());
22 
23         Thread t1 = new Thread(() -> {
24             // 循环加锁,加锁后list中的user都是t1线程的偏向锁
25             for (User user : userList) {
26                 synchronized (user) {
27  }
28  }
29             try {
30                 Thread.sleep(1000000);
31             } catch (InterruptedException e) {
32  e.printStackTrace();
33  }
34  });
35  t1.start();
36         // 保证t1中的for循环结束
37         Thread.sleep(3000);
38         System.out.println("after t1");
39         // 此时打印出来的对象头,全都是偏向t1线程的偏向锁
40         System.out.println(ClassLayout.parseInstance(userList.get(0)).toPrintable());
41 
42         Thread t2 = new Thread(() -> {
43             for (int i = 0; i < 40; i++) {
44                 // 在t2线程中循环加锁,加到第20个user的时候,会触发批量重偏向,第20个以及后面的对象变为偏向t2线程的偏向锁
45                 // 而之前的19个对象仍然为轻量级锁
46                 synchronized (userList.get(i)) {
47                     if (i == 18) {
48                         System.out.println("running t2: " + (i+1));
49  System.out.println(ClassLayout.parseInstance(userList.get(i)).toPrintable());
50  }
51  }
52  }
53             try {
54                 Thread.sleep(1000000);
55             } catch (InterruptedException e) {
56  e.printStackTrace();
57  }
58  });
59  t2.start();
60         Thread.sleep(3000);
61         System.out.println("after t2");
62         System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
63 
64         Thread t3 = new Thread(() -> {
65             for (int i = 20; i < 43; i++) {
66                 // 在t3线程中循环加锁,加到第20个user的时候,会触发批量重偏向,第20个以及后面的对象变为偏向t2线程的偏向锁
67                 // 而之前的19个对象仍然为轻量级锁
68                 synchronized (userList.get(i)) {
69                     if (i == 30 || i == 42) {
70                         System.out.println("running t3: " + (i+1));
71  System.out.println(ClassLayout.parseInstance(userList.get(i)).toPrintable());
72  }
73  }
74  }
75             try {
76                 Thread.sleep(1000000);
77             } catch (InterruptedException e) {
78  e.printStackTrace();
79  }
80  });
81  t3.start();
82         Thread.sleep(3000);
83         System.out.println("after t3");
84         System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
85  }
86 }
复制代码

打印结果:

复制代码
userList.get(0):
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

after t1
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 f0 1d 1b (00000101 11110000 00011101 00011011) (454946821)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t2: 19 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           b0 ef bd 1b (10110000 11101111 10111101 00011011) (465432496)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

after t2
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 01 00 00 (00000101 00000001 00000000 00000000) (261)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t3: 31 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           90 f4 cd 1b (10010000 11110100 11001101 00011011) (466482320)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

running t3: 43 com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           90 f4 cd 1b (10010000 11110100 11001101 00011011) (466482320)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

after t3
com.mybokeyuan.lockDemo.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     1               byte User.age                                  0
     13     3                    (alignment/padding gap)                  
     16     4   java.lang.String User.name                                 null
     20     4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
复制代码

 批量撤销触发的逻辑图如下所示:

 

 后记

    好了,synchronized的相关知识就到这里了,这个小系列的三篇,从使用场景,到锁类型的追踪、膨胀过程,再到批量重定向和批量撤销,都进行了说明。再延伸的话无非就是synchronized与volatile关键字的异同点比对之类的,没啥意思,大家百度一下即可。寒冷的冬天,还是要多加学习!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

回复列表