【并发】synchronized面试整理

线程同步方法都有哪些

互斥和同步的关系

  • 首先懂得同步和异步的关系 同步是按顺序执行要等待上一个执行完 异步是并发的
  • 互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法控制对资源的访问顺序
    同步是指在互斥的基础上实现对资源的有序访问

前置准备–理解一些名词含义

先来看锁对象

对象头

java对象=对象头+成员变量=objHeader+objBody
Integer 在32位虚拟机中 有64bits/8字节的对象头内存 还要加上数值4字节 共12字节
int 有4字节

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Monitor管程

每一个锁对象都对应一个monitor 简单举例每一个synchronized锁住的对象都对应着(是)一个操作系统的底层的锁monitor
在这里插入图片描述
当前对象的markword指向的是monitor,monitor当中有entryset . owner. waitset
获得锁对象的owner是他,其他线程来竞争就被阻塞放入entryset中,当前的owner出去进入waitset,owner也变成了空 就阻塞线程被唤醒
再来看抢夺锁对象的线程的内部

lock record对象

每一个线程都有自己的栈 在栈中的栈帧都会有一个记录线程抢夺、获得的锁的结构 该结构叫做lock record
里面记录着线程对应的锁对象的markword 和锁对象的地址
在这里插入图片描述
白话解释:线程和所抢夺的锁对象产生加锁关系 必须要互相知道彼此 所以lock record里面存着markword 然后obj 的 markword可以和lock record进行CAS交换 二者互留信物
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

抢夺锁的底层图

经常所说的锁升级 就是obj锁对象所指向的操作系统底层monitor对象会升级锁
随着多个线程对于锁对象object的markword抢夺 都希望CAS操作换成自己的lock record
此时就会让obj的markword指向升级的monitor
在这里插入图片描述
在这里插入图片描述当thread-0执行完临街区代码后,cas操作将markword交换回obj后 便找到obj指向的monitor将owner设置为null,唤醒阻塞队列里的thread-1

操作系统解释为什么synchronized效率低

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。所以每一次挂起线程 唤醒线程都要进行一个操作系用的映射。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,频繁出现程序运行状态的切换,线程的挂起和唤醒,这样就会大量消耗资源,程序运行的效率低下,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
在java6之前monitor对象都是操作系统的,jdk1.6之后jvm为我们提供了偏向锁和轻量级锁

Synchronized底层原理 synchronized修饰方法和代码块区别

  • 定义:synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
    引出底层
  • Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现方法同步和代码块同步,但两者的实现细节不一样。

  • 代码块同步是使用monitorenter和monitorexit指令实现的,线程执行monitorenter指令时尝试获取monitor的所有权,monitorexit退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
  • 方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
  • monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处, JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个 monitor 与之关联,当一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。
    在这里插入图片描述

介绍锁 锁升级 优缺点 使用场景

锁是为了多线程访问共享资源时 能够达到同步的效果 底层是CAS 目的既然是同步的 针对不同的访问共享资源 抢夺锁的情况 有不同的锁机制

  • 偏向锁:一般对象都是默认开启偏向锁的,它在应用程序启动几秒后才激活
    发明动机: 发现不止是多线程(不同线程)在对同一个锁抢夺,同一个线程下也会多次重复获得同一个锁,同一个线程获得锁没必要每一次都进行cas操作,代价太大为了让线程加锁解锁的代价更小,引入了偏向锁。
    简单来举例 偏向锁的场景,锁的重入 下图所示
    在这里插入图片描述

  • 加锁原理:
    偏行锁在对象头中的markword和栈帧的锁记录里中设置自己的线程ID 后三位是101 以后该线程再进入和退出同步代码块就不需要cas来加锁和解锁,只需要测试当前的对象的markword当中是与否有指向本线程的偏向锁即可。

  • 解锁原理:
    当有第二个线程来竞争锁的时候就会释放锁。其中有批量重偏向,0-19次的竞争都是先撤销第一个线程,重偏向第二个来竞争锁的线程,40次以上就批量撤销升级为轻量级锁了。
    具体实现 偏向锁撤销 101 000 001
    超过20次 重偏向 线程ID
    超过40次竞争 直接升级为轻量级锁
    在这里插入图片描述
    在这里插入图片描述


  • 轻量级锁:
    发明动机:若多线程都是对临界区交替请求,并没有产生竞争,那就希望不要都调用操作系统的重量级锁,带来状态切换的耗时以及性能的消耗。
  • 加锁:jvm会在栈帧中有一个锁记录和对象指针 指向锁对象 对象头中的markword里面的内容会复制一份Displaced Mark Word。线程尝试用cas将markword当中的内容和锁记录进行一个交换,如果交换成功线程获得轻量级锁,如果不成功当前线程就会自旋 或者 膨胀为重量级锁
  • 解锁:cas操作把displaced mark word复制回来 如果能够复制回来就解锁 不能升级为重量级锁

在轻量级锁中出现了自旋锁和自适应锁,所谓的自旋,就是让没有获得锁的线程自己运行一段时间的自循环,但是自旋不进入阻塞的代价就是一直占用Cpu,如果是短时间的自旋还好,若是长时间没有获得锁,就会消耗大量的Cpu资源必须挂起,通常是10次自旋就挂起了。JDK6之后又加入了自旋适应锁,他会根据这个锁的上一次自旋时间和拥有者的状态来判断自旋的时间。
在这里插入图片描述
在这里插入图片描述


好文 ---------------1

Synchronized实际开发用法注意

锁重入

  • 锁对象相同时 可以在一个函数调用其他相同锁对象的被synchronized修饰的函数
  • 支持父子继承 只要都是synchronized修饰 子可以重入父的函数

synchronized加在静态方法上锁对象是.class对象 Class类对象
synchronized加在函数上/this 锁的是.java对象

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值