synchronized已经不在臃肿了,放下对他的成见之初识轻量级锁

2023-10-29 16:40

本文主要是介绍synchronized已经不在臃肿了,放下对他的成见之初识轻量级锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

  • 物竞天择,适者生存。JDK也在不断的优化中。关于JDK中synchronized锁内部也是不断的优化,前面我们分析了偏向锁用来解决初期问题,随着争抢的不断堆积轻量级锁营运而生。
  • 关注我,一个不断进步的社畜码农,带你一起摆脱危机

轻量级锁

  • 上面说了没有竞争情况并且开启偏向锁的同时,才会产生偏向锁。但是偏向锁是不会主动撤销的。我们看下下面案列
  • vm配置如下-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
 public class SimpleTest {public static void main(String[] args) {SimpleTest test = new SimpleTest();System.out.println(ClassLayout.parseInstance(test).toPrintable());synchronized (test) {System.out.println("hello world");System.out.println(ClassLayout.parseInstance(test).toPrintable());}System.out.println("锁释放后:"+ClassLayout.parseInstance(test).toPrintable());​}}
  • 我们能够看到上锁前,上锁中,上锁后三个过程test对象中的markword一直都是偏向锁。这说明不会主动撤销

image-20211213143811117.png

  • 基于这个前提下,我们试想下有两个线程不同时间针对同一个对象上锁,这叫不叫资源竞争?因为不在同一时间运行期间实际上是交互进行的,但是因为偏向锁默认条件下是不会主动释放的。在偏向锁上锁流程是通过CAS将当前线程写入markword的,在写入之前是会进行对比锁对象markword是否是当前线程的。如果是和当前线程id一致的话,只会在计数器上加1 ,用于实现可重入式锁。
  • 如果是第二个线程不管是不是同时都会发生线程id不一致情况。这个时候就会发生偏向锁升级成轻量级锁。这个升级的过程也是很麻烦的过程。JVM实际上需要找到安全点(即线程不活动时间点)先撤销偏向锁,然后在上轻量级锁
偏向锁图示

image-20211213145511987.png

轻量级锁图示

image-20211213150317351.png

  • 通过图示我们也能够看的出来,偏向锁只会发生一次CAS, 而轻量级锁会无时无刻不发生CAS , 我们要知道CAS引发的线程自旋也是耗费CPU调度的,因为线程都处于活跃状态,那么CPU就会发生线程调度切换。所以在并发不是很高和普遍的项目中偏向锁是很搞笑的。
 ​class User{String userName;}public class SoftLock {public static void main(String[] args) throws InterruptedException {User user = new User();System.out.println("加锁前(禁用偏向延迟,此时应该是偏向锁默认):"+ClassLayout.parseInstance(user).toPrintable());final Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (user) {System.out.println("t1加锁中:" + ClassLayout.parseInstance(user).toPrintable());}}});t1.start();t1.join();final Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (user) {System.out.println("t1加锁中,因为t1加锁后线程偏向锁不会释放,所以t2会发生偏向锁撤销,最终t2轻量级锁:" + ClassLayout.parseInstance(user).toPrintable());}}});t2.start();t2.join();System.out.println("加锁后(无锁):"+ClassLayout.parseInstance(user).toPrintable());}}
  • 上述代码我们能够看出,在t2线程中尝试加锁就会变成轻量级锁。轻量级锁和偏向锁不同的是,轻量级锁使用后会释放锁,变成无锁状态
  • 当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
  • 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。
  • 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。
  • 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。
  • 如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。
  • 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
  • 多个线程在不同的时间段请求同一把锁,也就是说没有锁竞争。针对这种情形,Java 虚拟机采用了轻量级锁,来避免重量级锁的阻塞以及唤醒
  • 轻量级锁的条件是发生竞争或者是不得不上轻量级锁。下面我们看一种不得不上轻量级锁的案列 , 注意VM属性开启偏向锁延迟 及VM不做任何配置
 public class SimpleTest {public static void main(String[] args) {SimpleTest test = new SimpleTest();System.out.println(ClassLayout.parseInstance(test).toPrintable());synchronized (test) {System.out.println(ClassLayout.parseInstance(test).toPrintable());}}}
  • 这段代码和上面偏向锁演示匿名偏向锁的代码是一样的,不同的是VM的配置取消了。也就是开启了偏向锁延迟。那么我们第一次打印的test对象中markword中是无锁状态。按理说第二次就应该上偏向锁了。但是我们试想一下在第二次上偏向锁的时候延迟偏向也有可能会上偏向锁,这不就发生了资源争抢了吗,为了避免和延迟偏向发生冲突,所以第二次直接是轻量级锁。

image-20211215091052543.png

后续迭代推出重量级锁。

这篇关于synchronized已经不在臃肿了,放下对他的成见之初识轻量级锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/301936

相关文章

Linux操作系统 初识

在认识操作系统之前,我们首先来了解一下计算机的发展: 计算机的发展 世界上第一台计算机名叫埃尼阿克,诞生在1945年2月14日,用于军事用途。 后来因为计算机的优势和潜力巨大,计算机开始飞速发展,并产生了一个当时一直有效的定律:摩尔定律--当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。 那么相应的,计算机就会变得越来越快,越来越小型化。

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

docker学习系列(一)初识docker

在第一版本上线之后公司,我们决定将之前使用的开源api文档项目转移到本公司的服务器之上,之前用的是showdoc,showdoc利用的是php技术,作为java程序员表示需要快速部署php环境以及apach容器都需要时间,所以采用第二种方法,即利用docker进行快速部署(虽然学习成本也不比php少)。 一、docker简介 docker的官网是https://www.docker.com,

框架template初识

框架初识 框架就是一个别人帮我们搭好的舞台,造好了很多现成的工具供我们使用,让开发过程更快速、简洁。 Gin框架介绍 Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 Gin是一个用Go语言编写的web框架。它是一个类似于martini 但拥有更好性能的API框架, 由于使用了 httprouter,速度提高了近40倍。 第一个Gin示例 package mai

面试官:synchronized的锁升级过程是怎样的?

大家好,我是大明哥,一个专注「死磕 Java」系列创作的硬核程序员。 回答 在 JDK 1.6之前,synchronized 是一个重量级、效率比较低下的锁,但是在JDK 1.6后,JVM 为了提高锁的获取与释放效,,对 synchronized 进行了优化,引入了偏向锁和轻量级锁,至此,锁的状态有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。 锁升级就是无锁 —>

【数据结构】--初识泛型

1. 包装类 在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。 1.1 基本数据类型和对应的包装类 除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。 1.2 (自动)装箱和(自动)拆箱 装箱(装包): 把 基本数据类型 变为 包装类类型 的过程 叫做装箱。 反汇编指

初识Linux · 进度条

目录 前言: 1 缓冲区和回车换行 2 进度条 前言: 我们目前学习了些许知识,已经足够支持我们写一个非常非常小的项目了,即进度条,相信大家都有过下载游戏,等待游戏更新完成的时候,那么此时就有一个进度条,代表着游戏的更新进度,那么我们今天就来模拟实现这个过程,在此之前,我们需要一些预备知识。 1 缓冲区和回车换行 回车换行?是的,你没有看错,相信不少人对换行有一定的误解,我们

Linux初识线程

前言 前面在介绍进程的时候,说过进程的内核表述是"进程是承担资源分配的基本实体",但是我们至今都没有介绍如何理解他?本期我们就会介绍! 目录 前言 一、再谈地址空间和页表 1、OS对物理内存的管理 • 为什么4KB是OS进行I/O的基本单位? 2、再谈页表 • 二级页表 • 如何找到一个变量的所有字节? • 虚拟地址是如何转为物理地址的? • 理解动态内存管理 • 为什么对

【Java编程的思想】理解synchronized

用法和基本原理 synchronized可以用于修饰类的实例方法、静态方法和代码块 实例方法 在介绍并发基础知识的时候,有一部分是关于竞态条件的,当多个线程访问和操作同一个对象时,由于语句不是原子操作,所以得到了不正确的结果。这个地方就可以用synchronized进行处理 public class Counter {private int count;public synchroni

python+selenium2轻量级框架设计-04读取数据库

#操作sql server数据库 使用mysql则导入pymysqlimport pymssql,pymysqldb =pymssql.connect("localhost","sa","***","****")#使用cursor()方法获取操作游标cursor = db.cursor()sql = "****"try:#执行sqlcursor.execute(sql)#fetchon