【一道面试题】说一下Synchronized?

news2025/2/23 23:14:28

说一下Synchronized?

  • Synchronized锁是Java中为了解决线程安全问题的一种方式,是一种悲观锁
  • Synchronized可以用来修饰方法或者以代码块,用来保证线程执行方法或代码块时的原子性
  • Java中任何一个类的对象都可以用来作为锁对象,但是如果我们希望解决线程安全问题的话,那就必须要保证这些引发安全问题的线程使用的是同一把锁
  • 同步代码块的锁对象需要我们自己指定,一般如果是static方法的话,我们一般使用类的class对象作为锁对象,如果是非static方法的话,我们一般使用this作为锁对象;同步方法的锁对象不需要我们自己指定,如果同步方法是static的,则使用类的class对象。如果是非static的则使用this作为锁对象
  • 因为使用Synchronized锁会导致线程串行执行,所以建议其锁定的范围越小越好

Synchronized锁具体存放在什么位置?

Synchronized锁具体存放在对象头的MarkWord中

在Java中,一个对象由三个对象组成,分别是对象头、实例化数据和对齐填充字节,而对象头又包括三部分,分别是MarkWord、类型指针、数组长度(只有数组类型有)

以64位操作系统系统为例,MarkWord的内存结构如下:

在这里插入图片描述

说一下自旋锁?

所谓自旋锁,就是线程在获取锁失败时,不会立即进入阻塞状态,而是会尝试等待一段时间,由于这个等待是通过执行一段无意义的循环来完成的,故名自旋锁,如果线程自旋多次仍然没有获取到锁,才会进入阻塞状态。

如果没有自旋锁,那么线程一旦获取锁失败就会被阻塞,由于线程的阻塞和唤醒需要CPU从用户态转为内核态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力,而且在很多应用中,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。

自旋等待不能替代阻塞,先不说对处理器数量的要求(多核,貌似现在没有单核的处理器了),虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。

我们可以通过JVM参数开启或关闭自旋锁,或者设置自旋的默认次数。

JDK1.6引入自适应自旋锁,所谓自适应就意味着自旋的次数不是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的,线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。

什么是锁消除?

有些情况下,JVM检测到同步方法或同步代码块不可能存在共享数据竞争,这时JVM会对其同步锁进行锁消除,锁消除是在编译阶段完成的,JDK1.8中默认开启

说一下Synchronized的锁升级?

为了减少获得锁和释放锁而带来的性能消耗,Synchronized锁由低到高分为四种状态,分别是:无锁、偏向锁、轻量级锁、重量级锁,状态间的转换会随着竞争情况逐渐升级。除了偏向锁可以降级为无锁以外,锁只能升级但不能降级。

偏向锁

偏向锁引入的初衷是为了解决轻量级锁在没有竞争时每次重入仍需要CAS的问题,当一个线程试图获取偏向锁时,会首先对比偏向锁中记录的线程ID是否是自己,如果不是则会尝试通过一次CAS将自己的线程ID记录到锁对象的MarkWord中,一旦记录成功,那么之后无论是锁重入还是该线程再次获取到锁,都只需要比对线程ID是否是自己即可,不用重新 CAS。一般来说,如果不发生竞争问题的话,这把锁就归该对象所有了,故名偏向锁

如果我们开启了偏向锁(默认开始),那么使用Synchronized加锁时最开始其实就是偏向锁,但是偏向锁默认是延时生效的,如果我们希望立即生效也可以通过参数来设置

当偏向锁被撤销时会变成轻量级锁,偏向锁在以下情况时会被撤销:

  • 调用锁对象的hashCode方法:对象的hashCode也是存放在MarkWord中的,而且是在使用到的时候才会被加载,但是如果该对象是处于偏向锁状态的,那么它的MarkWord中是没有空间来存放hashCode的(原本存放hashCode的空间被存放线程ID的空间挤没了),故当我们调用锁对象的hashCode方法时,为了保存这个对象的hashCode值,偏向锁会撤销为轻量级锁
  • 锁竞争:当一个线程试图获取偏向锁,但是CAS失败时,就认为发生了锁竞争,这时候就会对该偏向锁进行撤销
  • 调用锁对象的wait/notify方法:wait/notify方法需要与底层操作系统打交道,因此无论是对象无论是在偏向锁状态下还是在轻量级锁状态下,调用wait/notify方法都需要升级成重量级锁
  • 批量撤销:如果一个锁对象撤销偏向锁的次数超过40次,那么JVM会把这个对象所对应的类的所有对象都撤销偏向锁,并且这个类新实例化的对象也是不可偏向的。这个操作其实就是禁用了这个类的可偏向属性

接下来讲一下偏向锁在撤销时会发生什么:

  • 偏向锁的撤销需要等待一个全局安全点(Safe Point,在这个时间点上没有任何字节码正在执行,即Stop The World)

  • 检查持有偏向锁的线程A是否存活

    • 如果仍存活,且正在执行同步代码块,则偏向锁直接升级为轻量级锁,且线程A获得该轻量级锁

    • 如果未存活,或未在执行同步代码块,则校验该锁是否允许重偏向

      • 如果不允许重偏向,则将Mark Word设置为无锁状态

      • 如果允许重偏向,则线程A释放偏向锁,偏向锁将被设置为匿名偏向状态

    当一把锁被撤销次数达到二十次时,jvm就会认为”自己的偏向可能出现了问题“,这时就会允许重偏向,即将该偏向锁设置为匿名偏向状态,如果锁撤销次数未达到二十次(应该)是不允许重偏向的

    匿名偏向状态也就是偏向锁还没有被任何线程获取时的状态,这个状态下线程可以通过CAS将线程ID记录到MarkWord中

轻量级锁

轻量级锁与偏向锁一样,同样是适用于锁没有竞争的场景下,线程获取偏向锁时是往锁对象的MarkWord中记录自己的ID,但在获取轻量级锁时进行的操作要复杂许多

假如说线程A现在要获取轻量级锁,那么具体过程如下:

  1. 线程A检查该锁的锁标志位是否为01,也就是判断锁状态是否为无锁,如果是则进入第二步,如果不是则进入第三步
  2. 在线程A的栈帧中创建一个名为锁记录(Lock Record)的空间,并把该锁对象的Mark Word拷贝一份到锁记录中,称之为Displaced Mark Word,然后线程A尝试通过CAS将锁记录的指针保存到锁对象的Mark Word中,并让锁记录的owner指针指向锁对象,如果能执行成功,那么此时加锁就成功了,锁标志位由01变成00,锁对象进入轻量级锁定状态;如果执行失败,则进入第三步。
  3. 线程A会去检查锁对象的Mark Word中是否保存了当前线程锁记录的指针,如果保存了,则说明出现了锁重入,这时会再在栈帧中创建一个锁纪录,假设这个锁记录叫锁记录B,之前的锁记录叫锁记录A,那么锁记录A与锁记录B的不同点在于,锁记录B不保存锁对象的Mark Word,也就是Displaced Mark Word为null,另外需要说明,轻量级锁重入的计数就是通过统计锁记录的个数得来的;如果没有保存,则进入第四步
  4. 线程A获取轻量级锁失败,一怒之下将该轻量级锁膨胀为重量级锁,锁标志位由00变成10,然后线程A会进行自旋来尝试获取锁(注意这个时候已经是重量级锁了),当自旋达到一定次数后,线程A进入该锁的阻塞队列

线程A释放轻量级锁的流程如下:

  1. 判断锁记录中的Displaced Mark Word是否为null,如果是,则说明是锁重入,移除owner的指向,不做替换操作;如果不是,则进入第二步
  2. 通过CAS把锁记录中的Displaced Mark Word与锁对象的对象头中的锁记录指针进行替换,如果替换成功,则轻量级锁解锁成功,该锁变成无锁状态;如果替换失败,则说明发生了锁膨胀,该对象现在处于重量级锁定状态,线程A进入重量级锁的解锁流程

重量级锁

重量级锁需要使用Monitor来完成,Monitor可以翻译为监视器或者管程,在HotSpot虚拟机中,Monitor是基于C++的ObjectMonitor类实现的,每个Java对象都可以关联一个Monitor对象,当我们把该对象作为重量级锁使用时,该对象的Mark Word中就会被设置一个指向Monitor对象的指针。Monitor对象中重要的属性有以下几个:

  • owner:指向持有锁的线程的锁记录
  • waitSet:存放处于wait状态的线程队列,即调用wait()方法的线程
  • entryList:存放处于等待锁block状态的线程队列
  • recursions:记录锁重入次数

仍然使用轻量级锁中的例子,线程A获取轻量级锁失败,轻量级锁膨胀为重量级锁,那么这中间发生了什么事呢?简单来说如下:

  1. 为锁对象申请Monitor对象,并将Monitor对象的地址设置到锁对象的Mark Word中
  2. 在Monitor的owner属性中设置持有锁线程的锁记录指针,锁记录仍指向锁对象的Mark Word,这一点保持不变
  3. 线程A自旋仍未获取锁,进入Monitor的entryList中

那么假如在上述案例中,轻量级锁的持有者是线程B,那么线程B释放锁时,进行的流程简单来说如下:

  1. CAS失败,发现锁已经膨胀为了重量级锁
  2. 通过锁对象的Mark Word找到Monitor对象的地址, 将其owner设置为null(这是在锁没有重入的情况下;若存在锁重入则recursions减一,且不执行下一步)
  3. 唤醒entryList中的线程,进行锁的竞争

那么假如线程A需要竞争该锁,那么它做的事情如下:

  1. 尝试通过CAS试修改Monitor的owner属性,若修改成功则表示获取锁成功,若修改失败则进入第二步
  2. 判断该锁的持有者是否为线程A,若是则线程A重入该锁,recursions加一,若不是则进入第三步
  3. 线程A进行自旋,达到一定次数后仍未获取锁,则再次进入entryList

entryList中的线程竞争锁时是非公平的,意思就是说先来的线程可能等了很久也获取不到锁,后来的线程可能刚到就获取到锁了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker-15-镜像Ubuntu20.04中安装python3.9

1 拉取并运行镜像 从docker hub 拉取镜像,以ubuntu20.04为例: docker pull ubuntu:20.04 docker run -it ubuntu:20.04 /bin/bash发现命令行变为root1234abcd5678:,这样就是进入docker容器里了。以下是docker常用的命令: # 以…

8086到80386汇编数据传送指令的扩展

80386及以上汇编的数据传送指令如下; MOV 传送字或字节. MOVSX 先符号扩展,再传送. MOVZX 先零扩展,再传送. PUSH 把字压入堆栈. POP 把字弹出堆栈. PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈. POPA 把DI,SI,BP,SP,BX,DX,CX,A…

人大金仓数据库KSQL常用命令

第三章KSQL常用命令 登陆前显示ksql的帮助命令 Ksql --help 列出所有的SQL命令清单 test# \h 列出某个SQL命令语法大纲 \h <sql命令> 如&#xff1a;\h delect 查看ksql元命令的帮助 ..... 查看数据库列表 显示当前连接的数据库和登录用户 \c 显示当前test数据库的…

数学和统计方法

平均数&#xff0c;加权平均数&#xff0c;中位数&#xff0c;众数 1、平均数&#xff1a;所有数加在一起求平均 2、中位数&#xff1a;对于有限的数集&#xff0c;可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个&#xff0c;通常取最中间的 …

Spring Boot学习篇(十一)

Spring Boot学习篇(十一) shiro安全框架使用篇(三) 1.shiro过滤地址配置(部分地址必须要登录才能访问) 1.1 在controll包下创建CRUDController类(用于提供地址进行测试),其内容如下所示 package com.zlz.controller;import org.springframework.stereotype.Controller; imp…

回顾一次后台从war包启动到jar包启动的改造

一、背景描述 1.项目情况 有个项目后台一开始是war包部署到tomcat中部署的 配置文件放在项目中 考虑到这种部署方式相对spring boot项目内置tomcat部署不太便捷&#xff0c;配置也没有独立出来&#xff0c;考虑将原来的spring mvc项目稍微改造为spring boot项目。 2.要求 1&am…

Linux设备树的概念

一.设备树概念以及作用1.设备树概念设备树(Device Tree)&#xff0c;将这个词分开就是“设备”和“树”&#xff0c;描述设备树的文件叫做 DTS(DeviceTree Source)&#xff0c;这个 DTS 文件采用树形结构描述板级设备&#xff0c;也就是开发板上的设备信息&#xff0c;比如CPU …

flowable的Task使用

ReceiveTask UserTask ServiceTask ScriptTask ReceiveTask 执行到这个ReceiveTask会停下来&#xff0c;需要人工触发一下&#xff0c;才会继续执行 ClassPathResource classPathResource new ClassPathResource("processes/ReceiveTaskDemo.bpmn20.xml");String f…

C++——模板与STL标准模板库

目录 一、模板 1.1类型模板 1.2非类型模板 二、STL 2.1链表实现 2.2迭代器 2.3STL容器 2.4STL算法 三、模板特化的匹配规则 (1) 类模板的匹配规则 (2) 函数模板的匹配规则 一、模板 1.1类型模板 #include <stdio.h> #include <iostream>using namespac…

深度学习 GAN生成对抗网络-手写数字生成及改良

如果你有一定神经网络的知识基础&#xff0c;想学习GAN生成对抗网络&#xff0c;可以按顺序参考系列文章&#xff1a; 深度学习 自动编码器与生成模型 深度学习 GAN生成对抗网络-1010格式数据生成简单案例 深度学习 GAN生成对抗网络-手写数字生成 一、前言 在前面一篇文章&am…

877. 石子游戏

877. 石子游戏题目算法设计&#xff1a;奇偶算法设计&#xff1a;动态规划题目 算法设计&#xff1a;奇偶 最简单的情况&#xff0c;只有2堆石子&#xff08;石子奇数&#xff09;&#xff0c;先稳赢。 但是四堆情况不同了&#xff0c;如 [3 7 2 3]。 不能直接选最大的&…

2023年五大趋势预测 | 大数据分析、人工智能和云产业展望

随着我们迈入2023年&#xff0c;大数据分析、人工智能和云产业将迎来蓬勃的创新和发展阶段 以下是我们预测的&#xff0c;将对行业格局产生重大影响的五大趋势&#xff1a; 世界在剧变&#xff0c;我们需要尽快寻找行业中的方向&#xff0c;迅速重回轨道 2023年&#xff0c;全…

TryHackMe-NahamStore(常见web漏洞 大杂烩)

NahamStore 漏洞赏金web安全 NahamStore的创建是为了测试您在NahamSec的“漏洞赏金狩猎和Web应用程序黑客入门”Udemy课程中学到的知识。 部署计算机&#xff0c;获得 IP 地址后&#xff0c;进入下一步&#xff01; 写在前面 可能我的顺序&#xff0c;跟别人以及题目都不太一…

spring boot集成activemq(windows)

目录 1.环境配置 2.说明 3.服务启动 4.示例 导入依赖 配置文件 service层 配置类 监听器 5.总结 1.环境配置 下载地址&#xff1a;https://activemq.apache.org/components/classic/download/安装&#xff1a;解压缩即可注意每个版本对应的java版本不一样&#xff0c…

分享96个PHP源码,总有一款适合您

PHP源码 分享96个PHP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 96个PHP源码下载链接&#xff1a;https://pan.baidu.com/s/1B-tNZlbfjT_D3n_Y6ZwfDw?pwduq19 提取码&#xff…

共享自助自习室棋p室茶室办公室电竞篮球馆小程序开发

共享自助自习室棋p室茶室办公室电竞篮球馆小程序开发 多场景应用的共享空间预约系统如:棋牌室;共享办公室&#xff0c;电竞篮球馆&#xff0c;自助民宿等。目前该应用已对接门锁和电控。 前端功能// 多场景应用、预约时间自定义、附近门店一目了然、支持门禁支持电控、首页门…

Windows系统安装轻量级高性能Web服务开发框架OAT++

一、软件简介 oat 是一个轻量级高性能 Web 服务开发框架&#xff0c;采用纯 C 编写而成。官网&#xff1a;https://oatpp.io/ 这个坑爹的网址在国内经常打不开&#xff0c;要多刷新几次。Github: https://github.com/oatpp/oatpp 当前版本&#xff1a; 1.3.0 其主要特性…

用PYTHON自动登录SAP GUI

我们都知道&#xff0c;SAP原生的“脚本录制和回放”功能是在用户进入到某一个SAP”用户指定系统“后才可以启用&#xff1a; 也就是说&#xff0c;从这里开始&#xff0c;您可以通过脚本录制&#xff0c;生成用户名、密码的输入和SAP登录过程的完整代码&#xff1b; 那么我们…

第三层:C++对象模型和this指针

文章目录前情回顾C对象模型和this指针类成员变量和类成员函数的储存this指针this指针概念this指针用途用途1解释用途2解释空指针调用成员函数const修饰的成员变量常函数内可以被修改的值突破&#xff01;步入第四层本章知识点&#xff08;图片形式&#xff09;&#x1f389;wel…

Matlab中算法结合Simulink求解直流微电网中功率

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…