synchronized锁机制(三)

el/2024/2/25 22:40:12

Java锁机制(一)

定义:

​ 锁的设计是为了解决线程并发安全问题,Java提供了两种锁来实现同步互斥访问 synchronizedlock

Monitor监视器锁

监视器锁

定义:

​ 任何一个对象都有一个Monitor与之关联,通过成对的MonitorEnter和MonitorExit指令来实现监视器锁。【便于理解重量级锁】

MonitorEnter

  1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1; 【可重入
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

MonitorExit

执行MonitorExit的线程必须是锁对象引用所对应的monitor的所有者。

  1. 指令执行时,monitor的进入数减1
  2. 如果减1后进入数为0,那线程退出monitor
  3. 其他被这个monitor阻塞的线程可以尝试去获取这monitor的所有权

synchronized

定义:

synchronized是一种对象锁,作用粒度是对象 【不是引用】

  • 内置锁
  • 可重入

使用位置:

  • 同步类方法【粒度大】
    • 锁是当前方法的类对象
  • 同步实例方法【粒度小】
    • 锁是当前方法的实例对象
  • 同步代码块【粒度可小】
    • 锁括号里面的对象

synchronized锁的优化:

  • 锁的膨胀升级
  • 锁的粗化
  • 锁的清除

synchronized膨胀升级

synchronized在Jdk1.6之前是直接进入重量级锁,jdk1.6之后引入了偏向锁、轻量级锁

synchronized实现偏向锁

定义:

​ 研究发现,在大多数情况下,锁不仅不会存在多线程竞争,而且总是由同一个线程多次获得。所以引入了偏向锁。

​ 这就意味着线程一开始进入同步代码块就调用操作系统原语mutex加锁 显然不合适。

实现:

  • 如果一个线程获得了锁,那么锁就进入偏向模式
  • 此时Mark Word的结构也变为偏向锁结构 【101
  • 当这个线程再次请求锁时,无需再做任何获取锁的过程
  • 在竞争激烈的情况下,升级为轻量级锁
// 默认开启偏向锁 JVM参数 
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking

一言蔽之:

无人竞争


synchronized实现轻量级锁

定义:

​ 若偏向锁失败,则锁升级为轻量级锁,由于==对绝大部分的锁,在整个同步周期内都不存在竞争== 即交替访问

实现:

  • 如果两个或多个线程之间交替执行同步块
  • 此时Mark Word的结构也变为轻量级锁的结构 【00
  • 在同一时刻存在多线程访问同步块,升级为自旋锁

一言蔽之:

多人轮流


synchronized实现自旋锁

定义:

​ 若轻量级锁失败,在大多数情况下,线程持有锁的时间都不会太长,JVM避免线程被真实阻塞,让其他线程循环等待的过程。

实现:

  • 如果两个或多个线程之间同时执行同步块
  • 此时没有拿到锁的线程进入自旋 【若干次空循环
  • 如果自旋过程拿到了锁,则进入临界区
  • 自旋时间过长,升级为重量级锁

一言蔽之:

多人并行,其他循环等待


synchronized实现重量级锁

synchronized字节码层面

定义:

​ 如果在轻量级锁中争抢激烈,没有抢到锁的线程会被阻塞。

阻塞和挂起的区别

阻塞和挂起都是放弃CPU执行权,挂起是将线程调出内存,存入辅存;阻塞时线程还在内存

实现:

  • 执行线程将先获取monitor
  • 此时Mark Word的结构也变为重量级锁的结构 【11
  • 获取成功之后才能执行方法体
  • 方法执行完后再释放monitor

获取和释放monitor都是通过JVM通过调用操作系统的互斥原语mutex来实现

一言蔽之:

多人并行,其他阻塞等待


锁升级流程

升级流程

synchronized 粗化和消除

锁优化 与 锁消除 都要基于 逃逸分析

锁优化

定义:

​ 有些情况下我们希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

例子:

public void doSomethingMethod(){synchronized(lock){//do some thing}// 其他代码,耗时短的代码synchronized(lock){//do other thing}
}for(int i=0;i<size;i++){synchronized(lock){}
}
// 粗化优化为下面👇
synchronized(lock){
for(int i=0;i<size;i++){}
}
锁消除

定义:

​ 锁消除是发生在编译器级别的一种锁优化方式,一些不用加锁的操作被人为加锁了,为了避免锁请求、同步、释放带来的性能损耗,编译器会将锁清除。

例子:

public class Demo {public static void main(String[] args) {for (int i = 0; i < 10000; i++) {createStringBuffer("Hyes", "为分享技术而生");}}public static String createStringBuffer(String str1, String str2) {StringBuffer sBuf = new StringBuffer();sBuf.append(str1);// append方法是同步操作sBuf.append(str2);return sBuf.toString();// 没有将StringBuffer返回,不会逃出该方法的生命周期}
}// StringBuffer源码
@Override
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

​ 源码中可以看出append()方法为同步方法,但是在外部调用时,createStringBuffer()方法在每次调用都会创建一个线程私有的sBuf,根本不需要加锁逻辑,那么编译器会将同步方法append()中的synchronized锁消除。

逃逸分析

定义:

**使用逃逸分析,**编译器可以对代码做如下优化:

  1. 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。【锁消除

  2. 将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。【锁消除

  3. 分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

是不是所有的对象和数组都会在堆内存分配空间?

​ 不一定,根据第三点,有些对象存储在CPU寄存器中

JVM参数:

-XX:+DoEscapeAnalysis // 开启逃逸分析,jdk1.7后默认开启
-XX:+EliminateLocks   // 开启锁消除
-XX:-DoEscapeAnalysis // 关闭逃逸分析

对象头补充

现在我们虚拟机基本是64位的,而64位的对象头有点浪费空间,JVM默认会开启指针压缩,所以基本上也是按32位的形式记录对象头

32位虚拟机的对象头

32位虚拟机的对象头

64位虚拟机的对象头

64位虚拟机对线头

JVM参数

-XX:+UseCompressedOops // 开启指针压缩
-XX:-UseCompressedOops // 关闭指针压缩

http://www.ngui.cc/el/4893707.html

相关文章

AQS框架详解(四)

AQS概述 定义 AbstractQueuedSynchronizer 抽象队列同步器是一个框架&#xff0c;它维护两种队列&#xff0c;基于一个状态量 state 来实现同步器&#xff0c;实现该框架时&#xff0c;仅仅需要实现几种方法就可以自定义同步器 两种队列 同步等待队列 CLH CLH队列是一种基于…

AQS框架应用(五)

AQS框架应用&#xff08;五&#xff09; 定义&#xff1a; ​ 基于AQS框架&#xff0c;Doug Lea给出了很多内置的实现类 实现类&#xff1a; 可以看出&#xff0c;有多个工具类是基于AQS框架实现的 独占式同步信号量 ReentrantLock Semaphore 共享式同步信号量 CountDown…

类加载机制(一)

类加载 定义&#xff1a; ​ 当 用到某个类 时&#xff0c;首先需要通过 类加载器 把类加载到JVM上 组成&#xff1a; 引导类加载器 —— 加载lib目录扩展类加载器 —— 加载ext目录应用程序类加载器 —— 加载ClassPath目录 【用户写的】自定义加载器 —— 加载用户自定义…

JVM内存模型(二)

JVM内存模型 定义&#xff1a; ​ JVM内存模型&#xff0c;又称为运行时数据区。【区别Java内存模型】 紫色 —— 线程私有 浅黄色 —— 线程共享 虚拟机栈 定义&#xff1a; ​ 虚拟机栈是一个线程私有的、FILO的结构。 ​ 一个线程到来时&#xff0c;从虚拟机栈中&…

JVM内存分配机制详解(三)

JVM对象创建过程 1.类加载检查 流程&#xff1a; JVM在遇到一条new指令检查这个指令的参数是否在常量池中定位到一个类的符号引用 【方法区】并检查 符号引用 所代表的类是否被类加载器加载 包括 new关键字、对象克隆、对象序列化等等 **总的来说&#xff1a;**执行一个类…

Spring IOC (三)

Spring IOC &#xff08;三&#xff09; refresh() 后八个方法 refresh() 定义&#xff1a; ​ 这个方法中&#xff0c;内置了13个方法&#xff0c;做了很多事情 方法&#xff1a; 方法作用prepareRefresh()保存了容器的启动时间&#xff0c;启动标志等obtainFreshBeanFac…

Spring循环依赖(四)

循环依赖 什么是循环依赖&#xff1f; 依赖&#xff1a;在Spring中&#xff0c;A类将其他的类当作属性时&#xff0c;就造成了A依赖其他类 定义&#xff1a; ​ 现有A、B两个类&#xff0c;如果A类将B类作为属性&#xff08;并设置Autowired&#xff09;&#xff0c;B类也将…

想要学习Python?8年老Python程序员的自学书单曝光,入门必看

什么是Python&#xff1f; 它是一种类似C语言的人工智能便捷语言。它简单易学、并且开源免费&#xff0c;应用面广&#xff0c;所以&#xff0c;是自学门槛相对低的一种计算机语言&#xff0c;能为你的生活、工作提升效率。 为什么选择学习python的人越来越多&#xff1f; 全…

Python基础学习:都给我学起来,这样快速处理CSV、JSON和XML数据的方法太简便了

Python的卓越灵活性和易用性使其成为最受欢迎的编程语言之一&#xff0c;尤其是对于数据处理和机器学习方面来说&#xff0c;其强大的数据处理库和算法库使得python成为入门数据科学的首选语言。在日常使用中&#xff0c;CSV&#xff0c;JSON和XML三种数据格式占据主导地位。下…

Python 强大的信号库:必看的blinker 入门级教程,简单易懂

1 信号 信号是一种通知或者说通信的方式&#xff0c;信号分为发送方和接收方。发送方发送一种信号&#xff0c;接收方收到信号的进程会跳入信号处理函数&#xff0c;执行完后再跳回原来的位置继续执行。 常见的 Linux 中的信号&#xff0c;通过键盘输入 CtrlC&#xff0c;就是…