我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JAVA偏向锁的原理与实战

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JAVA偏向锁的原理与实战

1. 偏向锁的核心原理

如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark Word的结构变为偏向锁结构,锁对象的锁标志位(lock)被改为01,偏向标志位(biased_lock)被改为1,然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完成)。以后该线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。

关键点:无竞争

缺点:如果锁对象时常被多个线程竞争,偏向锁就是多余的,并且其撤销的过程会带来一些性能开销

2. 偏向锁代码演示

偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数

-XX:BiasedLockingStartupDelay=0 来禁用延迟

package innerlock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class InnerLockTest {
	int a=1;
	double b=1.1;
	public static void main(String[] args) {
		System.out.println(VM.current().details());
		Person person=new Person();
		ClassLayout layout=ClassLayout.parseInstance(person);
		new Thread(()->{
			System.out.println("获取偏向锁前:");
			System.out.println(layout.toPrintable());
			synchronized (person) {
				System.out.println("获取偏向锁中:");
				System.out.println(layout.toPrintable());
			}
			System.out.println("获取偏向锁结束后:");
			System.out.println(layout.toPrintable());
		}
		,"thread1").start();
	}
}
class Person{
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

禁用偏向锁:添加 VM 参数 -XX:-UseBiasedLocking

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. 偏向锁的膨胀与撤销

假如有多个线程来竞争偏向锁,此对象锁已经有所偏向,其他的线程发现偏向锁并不是偏向自己,就说明存在了竞争,尝试撤销偏向锁(很可能引入安全点),然后膨胀到轻量级锁

1. 偏向锁的撤销

1.在一个安全点停止拥有锁的线程

2.遍历线程的栈帧,检查是否存在锁记录。如果存在锁记录,就需要清空锁记录,使其变成无锁状态,并修复锁记录指向的Mark Word,清除其线程ID

3.将当前锁升级成轻量级锁

4.唤醒当前线程

撤销偏向锁的条件(满足其一即可):

1.多个线程竞争偏向锁

2.调用偏向锁对象的hashcode()方法或者System.identityHashCode()方法计算对象的HashCode之后,将哈希码放置到Mark Word中,内置锁变成无锁状态,偏向锁将被撤销

2. 批量重偏向与撤销

批量重偏向解决的问题:

一个线程创建了大量对象并执行了初始的同步操作,之后在另一个线程中将这些对象作为锁进行之后的操作。这种case下,会导致大量的偏向锁撤销操作。

package innerlock;
import java.util.ArrayList;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class InnerLockTest {
	int a=1;
	double b=1.1;
	public static void main(String[] args) throws InterruptedException {
		System.out.println(VM.current().details());
		ArrayList<Person> list=new ArrayList<Person>();
		new Thread(()->{
			for(int i=0;i<100;i++)
			{
				Person person=new Person();
				synchronized (person) {
					list.add(person);
				}
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		,"thread1").start();
		Thread.sleep(3000);
		new Thread(()->{
			for(int i=0;i<30;i++)
			{
				Person person=list.get(i);
				synchronized (person) {
					if(i==17||i==18||i==19||i==21)
					{
						System.out.println("第"+(i+1)+"次偏向结果:");
						System.out.println(ClassLayout.parseInstance(person).toPrintable());
					}
				}
			}
		}
		,"thread2").start();
	}
}
class Person{
}

在这里插入图片描述

在这里插入图片描述

结果分析:

先用线程1创建了100个对象锁,这些对象锁都偏向于线程1,后面创建线程2去争夺这些锁,前19次线程2都是抢占失败获得轻量级锁(失败过程中阈值增加),第20次抢占时达到阈值20,这时JVM会认为自己是不是不应该偏向线程1,于是之后开始偏向线程2,线程2之后获得的都是偏向锁

  • 第1-19个对象由于线程2在抢占过程中变为轻量级锁,锁释放后变为无锁状态
  • 第20-30个对象触发批量重定向,锁释放后依旧偏向线程2
  • 第31-100个对象依然和开始一样偏向线程1,锁释放后依旧偏向线程1

批量撤销解决的问题:

存在明显多线程竞争的场景下使用偏向锁是不合适的,例如生产者/消费者队列

package innerlock;
import java.util.ArrayList;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class InnerLockTest {
	int a=1;
	double b=1.1;
	public static void main(String[] args) throws InterruptedException {
		System.out.println(VM.current().details());
		ArrayList<Person> list=new ArrayList<Person>();
		new Thread(()->{
			for(int i=0;i<100;i++)
			{
				Person person=new Person();
				synchronized (person) {
					list.add(person);
				}
			}
			try {
			//为了防止JVM线程复用,在创建完对象后,保持线程t1状态为存活
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		,"thread1").start();
		Thread.sleep(3000);
		new Thread(()->{
			for(int i=0;i<40;i++)
			{
				Person person=list.get(i);
				synchronized (person) {
					if(i==18||i==19||i==39||i==41)
					{
						System.out.println("t2  第"+(i+1)+"次偏向结果:");
						System.out.println(ClassLayout.parseInstance(person).toPrintable());
					}
				}
			}
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		,"thread2").start();
		Thread.sleep(3000);
		new Thread(()->{
			for(int i=20;i<40;i++)
			{
				Person person=list.get(i);
				synchronized (person) {
					if(i==20||i==39)
					{
						System.out.println("t3   第"+(i+1)+"次偏向结果:");
						System.out.println(ClassLayout.parseInstance(person).toPrintable());
					}
				}
			}
		}
		,"thread3").start();
		Thread.sleep(1000);
		System.out.println("新创建对象:"+ClassLayout.parseInstance(new Person()).toPrintable());
	}
}
class Person{
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

做法:

以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值默认20时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id 改成当前线程Id

当达到重偏向阈值后,假设该class计数器继续增长,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑

小结:

在这里插入图片描述

3. 偏向锁的膨胀

如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表明在这个对象锁上已经存在竞争了。JVM检查原来持有该对象锁的占有线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进行重新偏向,偏向抢锁线程。如果JVM检查到原来的线程依然存活,就进一步检查占有线程的调用堆栈是否通过锁记录持有偏向锁。如果存在锁记录,就表明原来的线程还在使用偏向锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JAVA偏向锁的原理与实战

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java synchronized偏向锁的核心原理是什么

本篇内容主要讲解“Java synchronized偏向锁的核心原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java synchronized偏向锁的核心原理是什么”吧!1. 偏向锁
2023-06-29

详解Java ReentrantReadWriteLock读写锁的原理与实现

ReentrantReadWriteLock读写锁是使用AQS的集大成者,用了独占模式和共享模式。本文和大家一起理解下ReentrantReadWriteLock读写锁的实现原理,需要的可以了解一下
2022-11-13

MySQL 锁的原理与应用实践

MySQL 锁的原理与应用实践摘要:MySQL 是一种常用的关系型数据库管理系统,它具有强大的并发处理能力。在多用户同时访问数据库时,为了确保数据的一致性和完整性,MySQL 使用锁机制来控制对共享资源的访问。本文将介绍 MySQL 锁的原
MySQL 锁的原理与应用实践
2023-12-21

Java 重入锁实现的原理究竟是什么?(java重入锁实现的原理是什么)

在Java编程中,重入锁(ReentrantLock)是一种非常重要的同步机制,它在实现线程安全的同时,提供了比synchronized关键字更灵活的控制方式。本文将深入探讨Java重入锁实现的原理,帮助你更好地理解和使用这一机制。一、重入锁的基本概
Java 重入锁实现的原理究竟是什么?(java重入锁实现的原理是什么)
Java2024-12-21

Java公平锁与非公平锁的核心原理讲解

从公平的角度来说,Java中的锁总共可分为两类:公平锁和非公平锁。但公平锁和非公平锁有哪些区别?核心原理是什么?本文就来和大家详细聊聊
2022-11-13

Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结

Redis 分布式锁使用 SET 指令就可以实现了么?在分布式领域 CAP 理论一直存在。分布式锁的门道可没那么简单,我们在网上看到的分布式锁方案可能是有问题的。

Java中锁的实现原理和实例用法

这篇文章主要介绍“Java中锁的实现原理和实例用法”,在日常操作中,相信很多人在Java中锁的实现原理和实例用法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中锁的实现原理和实例用法”的疑惑有所帮助!
2023-06-16

深入剖析OpenMP锁的原理与实现

在本篇文章当中主要给大家介绍一下 OpenMP 当中经常使用到的锁并且仔细分析它其中的内部原理!文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
2023-01-28

Java实现读写锁的原理是什么

本文小编为大家详细介绍“Java实现读写锁的原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java实现读写锁的原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。读/写锁Java实现首先我们总结一
2023-06-29

Java关键字synchronized原理与锁的状态详解

在Java当中synchronized关键字通常是用来标记一个方法或者代码块。本文将通过示例为大家详细介绍一下Synchronized的各种使用方法,需要的可以参考一下
2022-11-13

详解Redis分布式锁的原理与实现

目录前言使用场景为什么要使用分布式锁如何使用分布式锁流程图分布式锁的状态分布式锁的特点分布式锁的实现方式(以redis分布式锁实现为例)总结前言在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁
2022-06-23

Java面向对象之多态的原理是什么与怎么实现

本文小编为大家详细介绍“Java面向对象之多态的原理是什么与怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java面向对象之多态的原理是什么与怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。何为多
2023-06-30

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录