前言

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

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

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

    前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实 ...

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

    前言 近期研读路神之绝世武学,徜徉于浩瀚无垠知识之海洋,偶有攫取吉光片羽,惶恐未领略其精髓即隐入岁月深处,遂急忙记录一二,顺备来日吹cow之谈资.本小系列为并发之亲儿子-独臂狂侠synchronize ...

  3. Java并发之synchronized关键字

         上篇文章我们主要介绍了并发的基本思想以及线程的基本知识,通过多线程我们可以实现对计算机资源的充分利用,但是在最后我们也说明了多线程给程序带来的两种典型的问题,针对它们,synchronize ...

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

    欢迎点赞阅读,一同学习交流,有疑问请留言 . GitHub上也有开源 JavaHouse,欢迎star 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦 ...

  5. 深入理解Java并发之synchronized实现原理

    深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...

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

    巨人大哥谈Java中的Synchronized关键字用法 认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方价格synchronized基本上就 ...

  7. 并发之synchronized关键字的应用

    并发之synchronized关键字的应用 synchronized关键字理论基础 前两章我们学习了下java内存模型的相关知识, 现在我们来讲讲逢并发必出现的synchronized关键字. 作用 ...

  8. Java并发之synchronized

    Java多线程同步关键词是常用的多线程同步手段.它可以修饰静态类方法,实例方法,或代码块.修饰static静态方法时是对整个类加锁. 一.实现原理 在JVM中对象内存分三块区域,对象头.实例数据.对齐 ...

  9. Java进阶1. Synchronized 关键字

    Java进阶1. Synchronized 关键字 20131025 1.关于synchronized的简介: Synchronized 关键字代表对这个方法加锁,相当于不管那一个线程,运行到这个方法 ...

随机推荐

  1. Android状态栏兼容4.4.4与5.0,Android5.0状态栏由半透明设置为全透明

    //判断android 版本然后设置Systembar颜色 public void initSystemBar() { Window window = getWindow(); //4.4版本及以上 ...

  2. linux字符集修改

    首先介绍一下变量. 1.变量类型:本地变量.环境变量.局部变量.特殊变量(内置).参数变量.只读变量. 2.bash的配置文件:profile类和bashrc类 profile类:为交互式登录的she ...

  3. 网页解析之BeautifulSoup

    介绍及安装 Beautiful Soup 是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据. BeautifulSoup 用来解析 HTML 比较简单,API非常人 ...

  4. sed和awk的简单使用

    sed是一个很好的文件处理工具,本身是一个管道命令,主要是以  行 为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法. 语法: sed [-nefri] ‘ ...

  5. Linux 命令之 scp 命令详解

    Linux 命令之 scp 命令详解 一.scp 简介 scp 命令用于不同主机之间复制文件和目录. scp 是 secure copy 的缩写,是 基于 ssh 协议进行安全的远程文件拷贝命令. s ...

  6. day20191001国庆默写

    day20191001国庆默写恢复 重在理解,而不是死记硬背.认真专心看6遍,做6遍. 学会码字,每天码字二小时.持之以恒. 任重道远,出发,走多少算多少.100分的试卷,会做20分也比一个努力也没有 ...

  7. 浅析babel产出

    (function(modules) { // 缓存对象 var installedModules = {}; // require方法 function __webpack_require__(mo ...

  8. 华为ARM64服务器上手体验--不吹不黑,用实际应用来看看TaiShan鲲鹏的表现

    背景 中美贸易冲突以来,相信最大的感受,并不是我对你加多少关税,而是我有,可我不卖给你."禁售"成了市场经济中最大的竞争力. 相信也是因为这个原因,华为"备胎转正&quo ...

  9. java基础-泛型举例详解

    泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...

  10. 洛谷 P3420 [POI2005]SKA-Piggy Banks 题解

    蒟蒻的第二篇题解 嗯,直接进入正题 先告诉你们这是并查集,好吧,标签上面有,再来分析这为什么是并查集. 根据题意: 每一个存钱罐能够用相应的钥匙打开或者被砸开,Byteazar已经将钥匙放入到一些存钱 ...