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

原文地址:https://www.cnblogs.com/zzq6032010/p/11967179.html

时间: 2024-10-17 03:45:13

Java并发之synchronized关键字深度解析(三)的相关文章

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

前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实现的给对象加锁?首先我们要了解一下java中对象的组成.java中的对象由3部分组成,第一部分是对象头,第二部分是实例数据,第三部分是对齐填充. 对齐填充:jvm规定对象的起始内存地址必须是8字节的整数倍,如果不够的话就用占位符来填充,此部分占位符就是对齐填充: 实例数据:实例数据是对象存储的真正有

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

前言 近期研读路神之绝世武学,徜徉于浩瀚无垠知识之海洋,偶有攫取吉光片羽,惶恐未领略其精髓即隐入岁月深处,遂急忙记录一二,顺备来日吹cow之谈资.本小系列为并发之亲儿子-独臂狂侠synchronized专场. 一.使用场景 synchronized是java中的一个关键字,用于给对象加锁,保证在单机的并发条件下线程安全.原子性和可见性是它保证线程安全的基础功能.一定要注意它锁的是对象,而且它是一个排他的非公平可重入锁.本文先从使用的场景上来展现其作用. 1.用在普通方法中 常用的格式如下所示:

Java并发之synchronized关键字和Lock接口

欢迎点赞阅读,一同学习交流,有疑问请留言 . GitHub上也有开源 JavaHouse,欢迎star 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦下来,只允许一个人过独木桥.书面意思就是将并行的程序变成串行的程序.现实的锁有门锁.挂锁和抽屉锁等等.在Java中,我们的锁就是synchronized关键字和Lock接口. synchronized关键字 synchronized也叫同步锁,是Java里面的关键字.我们可以猜测到synchroni

JAVA并发之synchronized关键字

简介 synchronizaed关键字是JAVA阻塞同步(互斥同步)中最常用的一种方式,使用时将此关键字加到所需同步的代码块儿前即可,比如 int i = 0; synchronized (this){ i++; } synchronizaed同步方式在JAVA中是重量级加锁方式,下面来介绍一下它的工作原理,首先写一段代码: public class Sync { synchronized void syncMethod(){} public void add(){ synchronized (

巨人大哥谈Java中的Synchronized关键字用法

巨人大哥谈Java中的Synchronized关键字用法 认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方价格synchronized基本上就搞定 了,如果说不考虑性能问题的话,这一操绝对能应对百分之九十以上的情况,若对于性能方面有要求的话就需要额外的知识比如读写锁等等.本文目的先了解透彻synchronized的基本原理. Synchronized的基本使用 Synchronized的作用主要有三个: (1)确保线程互斥的访问同步代码 

Java多线程:synchronized关键字和Lock

一.synchronized synchronized关键字可以用于声明方法,也可以用来声明代码块,下面分别看一下具体的场景(摘抄自<大型网站系统与Java中间件实践>) 案例一:其中foo1和foo2是SynchronizedDemo1类的两个静态方法.在不同的线程中,这两个方法的调用是互斥的,不仅是它们之间,任何两个不同线程的调用也互斥. public class SynchronizedDemo1 { public synchronized static void foo1(){} pu

java并发之synchronized详解

前言 多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU

Java中的Synchronized关键字用法

认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方加上synchronized修饰符基本上就搞定 了,如果说不考虑性能问题的话,这一招绝对能应对百分之九十以上的情况,若对于性能方面有要求的话就需要额外的知识比如读写锁等等.本文目的先了解透彻synchronized的基本原理. Synchronized的基本使用 Synchronized的作用主要有三个: (1)确保线程互斥的访问同步代码 (2)保证共享变量的修改能够及时可见 (3)有效解决

java并发之volatile关键字(TIJ21-21.3.3 21.3.4)

** 1 简介    volatile是java中的一个保留关键字,它在英语中的含义是易变的,不稳定的.volatile像final.static等其他修饰符 一样,可以修饰class中的域,而不能修饰方法中的局部变量.当修饰class中的域时,volatile可以修饰primative类型或者任意对 象.下面这个例子展示了这一点: public class TIJ_volatile {    private volatile int i;    private volatile String