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


    前言

        本篇主要介绍一下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关键字的异同点比对之类的,没啥意思,大家百度一下即可。寒冷的冬天,还是要多加学习!

  • 相关阅读:
    ZedBoard学习(6)System Generator实现串口通信(一行HDL代码都不用写)
    ZedBoard学习(1)Ubutun下进行串口通信
    Zedboard学习(7)PS下第一个裸奔程序
    激光雷达(一)数据采集C++
    win7/win8下安装Oracle1出错10g,提示“程序异常终止,发生未知错误”解决方法
    XML文件的加密与解密
    三层中最重要的SqlHelper类
    创建桌面快捷方式的语法
    秋招总结 艾尔夏尔
    thoughtworks二面准备 (三) 艾尔夏尔
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/11967179.html
Copyright © 2020-2023  润新知