synchronized原理
synchronized用于多线程访问共享资源时,通过同步机制保证线程安全。包括同步代码块、同步方法、静态同步方法,详见《Java多线程(下)》。本章介绍下synchronized原理。
synchronized是JDK自带的一个关键字,在JDK1.5之前是一个重量级锁,会切换线程状态,导致性能不好,所以从性能上考虑大部分人会选择Lock锁,不过毕竟是JDK自带的关键字,所以在JDK1.6后对它进行优化,引入了偏向锁,轻量级锁,自旋锁等概念。
synchronized四大特性
原子性
synchronized修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断,即保证了原子性。可见性
synchronized对一个类或对象加锁时,锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性。有序性
synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。可重入性
synchronized和ReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己已经持有对象锁的临界资源时,可直接调用,这种情况属于重入锁。
可重入性最大的作用是避免死锁。
synchronized锁的原理
由之前文章《JVM-Java Virtual Machine(Java虚拟机)》,在JVM中,对象在内存中分为三块区域:对象头,实例数据和对齐填充。
对象头:
- Mark Word,用于存储对象自身运行时的数据,如哈希码(Hash Code),GC分代年龄,锁状态标志,偏向线程ID、偏向时间戳等信息,它会根据对象的状态复用自己的存储空间。它是实现偏向锁和轻量级锁的关键。
- 类型指针,对象会指向它的类的元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。
- Array length,如果对象是一个数组,还必须记录数组长度的数据。
1、同步代码块原理
为了看底层实现原理,使用javap -v xxx.class命令进行反编译。
这是使用同步代码块被标志的地方就是刚刚提到的对象头,它会关联一个monitor对象,也就是括号括起来的对象。
1、monitorenter,如果当前monitor的进入数为0时,线程就会进入monitor,并且把进入数+1,那么该线程就是monitor的拥有者(owner)。
2、如果该线程已经是monitor的拥有者,又重新进入,就会把进入数再次+1。也就是可重入性。
3、monitorexit,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的进入数减1,如果减1后进入数为0,则该线程会退出monitor。其他被阻塞的线程就可以尝试去获取monitor的所有权。
总的来说,synchronized的底层原理是通过monitor对象来完成的。
2、同步方法原理
synchronized修饰的实例方法:
public synchronized void hello(){System.out.println("hello world");
}
javap -v反编译:
可以看到多了一个标志位ACC_SYNCHRONIZED,作用就是一旦执行到这个方法时,就会先判断是否有标志位,如果有这个标志位,就会先尝试获取monitor,获取成功才能执行方法,方法执行完成后再释放monitor。在方法执行期间,其他线程都无法获取同一个monitor。归根结底还是对monitor对象的争夺。
synchronized锁的优化
JDK1.5之前,synchronized是属于重量级锁,重量级需要依赖于底层操作系统的 互斥锁(Mutex Lock)实现的,然后操作系统需要切换用户态和内核态,这种切换的消耗非常大,所以性能相对来说并不好。
JDK1.6后开始对synchronized进行优化,增加了锁消除、锁粗化、偏向锁、轻量级锁、自适应的CAS自旋这些优化策略。锁的等级从 无锁->偏向锁->轻量级锁->重量级锁 逐步升级,并且是单向的,不会出现锁的降级。
锁消除
锁消除是一种锁的优化策略,这种优化更加彻底,在JVM编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。 这种优化策略可以消除没有必要的锁,节省毫无意义的请求锁时间。比如StringBuffer的append()方法,就是使用synchronized进行加锁的。
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}
如果在实例方法中StringBuffer作为局部变量使用append()方法,StringBuffer是不可能存在共享资源竞争的,因此会自动将其锁消除。
public String add(String s1, String s2) {//sb属于不可能共享的资源,JVM会自动消除内部的锁StringBuffer sb = new StringBuffer();sb.append(s1).append(s2);return sb.toString();
}
锁粗化
如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,所以引入锁粗话的概念。意思是将多个连续加锁、解锁的操作连接在一起,扩展成为一个范围更大的锁。
偏向锁
JDK的开发人员经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。也就是说在很多时候我们是假设有多线程的场景,但是实际上却是单线程的。所以偏向锁是在单线程执行代码块时使用的机制。
原理是什么呢,我们前面提到锁的争夺实际上是Monitor对象的争夺,还有每个对象都有一个对象头,对象头是由Mark Word和Klass pointer 组成的。一旦有线程持有了这个锁对象,标志位修改为1,就进入偏向模式,同时会把这个线程的ID记录在对象的Mark Word中,当同一个线程再次进入时,就不再进行同步操作,这样就省去了大量的锁申请的操作,从而提高了性能。
自适应的CAS自旋
上面说过,当线程没有获得monitor对象的所有权时,就会进入阻塞,当持有锁的线程释放了锁,当前线程才可以再去竞争锁,但是如果按照这样的规则,就会浪费大量的性能在阻塞和唤醒的切换上,特别是线程占用锁的时间很短的话。
为了避免阻塞和唤醒的切换,在没有获得锁的时候就不进入阻塞,而是不断地循环检测锁是否被释放,这就是自旋。在占用锁的时间短的情况下,自旋锁表现的性能是很高的。
但是,由于线程是一直在循环检测锁的状态,就会占用cpu资源,如果线程占用锁的时间比较长,那么自旋的次数就会变多,占用cpu时间变长导致性能变差。那自旋次数设置多少比较合适呢?JDK1.6引入了自适应的CAS自旋。
自适应性自旋锁的意思是,自旋的次数不是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
轻量级锁
如果获取偏向锁失败,也就是有多个线程竞争锁的话,就会升级为JDK1.6引入的轻量级锁。
轻量级锁的获取流程:
首先判断当前对象是否处于一个无锁的状态,如果是,Java虚拟机将在当前线程的栈帧建立一个锁记录(Lock Record),用于存储对象目前的Mark Word的拷贝,如图所示。
将对象的Mark Word复制到栈帧中的Lock Record中,并将Lock Record中的owner指向当前对象,并使用CAS操作 将对象的Mark Word更新为指向Lock Record的指针,如图所示。
- 如果第二步执行成功,表示该线程获得了这个对象的锁,将对象Mark Word中锁的标志位设置为“00”,执行同步代码块。
- 如果第二步未执行成功,需要先判断当前对象的Mark Word是否指向当前线程的栈帧,如果是,表示当前线程已经持有了当前对象的锁,这是一次重入,直接执行同步代码块。如果不是表示多个线程存在竞争,该线程通过自旋尝试获得锁,即重复步骤2,自旋超过一定次数,轻量级锁升级为重量级锁,也就是把线程阻塞起来,等待唤醒。
轻量级锁的解锁:
- 轻量级的解锁同样是通过CAS操作进行的,线程会通过CAS操作将Lock Record中的Mark Word替换回来。如果成功表示没有竞争发生,成功释放锁,恢复到无锁的状态;如果失败,表示当前锁存在竞争,升级为重量级锁。
一句话总结轻量级锁的原理:将对象的Mark Word复制到当前线程的Lock Record中,并将对象的Mark Word更新为指向Lock Record的指针。
重量级锁
重量级锁就是一个悲观锁了,但是其实不是最坏的锁,因为升级到重量级锁,是因为线程占用锁的时间长(自旋获取失败),锁竞争激烈的场景,在这种情况下,让线程进入阻塞状态,进入阻塞队列,能减少cpu消耗。
小结
- 偏向锁:适用于单线程执行。
- 轻量级锁:适用于锁竞争较不激烈的情况。
- 重量级锁:适用于锁竞争激烈的情况。
学习synchronized关键字的底层原理不是钻牛角尖,其实是从底层原理上知道了synchronized在什么场景使用会有什么样的效果,我们都知道没有最好的技术,只有最适合的技术,所以说在不同的场景使用最佳的解决方案才是最好的技术。
synchronized在不同的场景会自动选择不同的锁,这样一个升级锁的策略就体现出了这点。
参考文章:
synchronized底层原理是什么?
synchronized原理相关推荐
- 打工人,从 JMM 透析 volatile 与 synchronized 原理
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 在面试.并发编程.一些开源框架中总是会遇到 volatil ...
- StringBuffer、StringBuilder区别以及Synchronized原理
1.为什么StringBuffer是线程安全的StringBuilder是线程不安全的 这里我只是列举了几个方法来对比,其他方法对比可以查看两个类的源码,从上面的截图可以看到StringBuffer实 ...
- 并发编程之Synchronized原理
一.基本使用 1.Synchronized的作用. 原子性:确保线程互斥的访问同步代码: 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 "对一个变量unloc ...
- Java 并发编程CAS、volatile、synchronized原理详解
CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...
- 【重难点】【JUC 04】synchronized 原理、ReentrantLock 原理、synchronized 和 Lock 的对比、CAS 无锁原理
[重难点][JUC 04]synchronized 原理.ReentrantLock 原理.synchronized 和 Lock 的对比.CAS 无锁原理 文章目录 [重难点][JUC 04]syn ...
- 从 JMM 透析 volatile 与 synchronized 原理
作者 | 李健青 来源 | 码哥字节(ID:MageByte) 头图 | CSDN 下载自东方IC 在面试.并发编程.一些开源框架中总是会遇到 volatile 与 synchronized .sy ...
- Synchronized原理(偏向锁篇)
Synchronized原理(偏向锁篇) 传统的锁机制 传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换.进程的上 ...
- java虚拟机线程调优与底层原理分析_啃碎并发(七):深入分析Synchronized原理...
原标题:啃碎并发(七):深入分析Synchronized原理 前言 记得开始学习Java的时候,一遇到多线程情况就使用synchronized,相对于当时的我们来说synchronized是这么的神奇 ...
- 锁,CAS,Synchronized 原理
作者:~小明学编程 文章专栏:JavaEE 格言:热爱编程的,终将被编程所厚爱. 目录 常见的锁 悲观锁与乐观锁 悲观锁 乐观锁 读写锁 重量级锁 vs 轻量级锁 挂起等待锁和自旋锁 公平锁和非公平锁 ...
- 锁策略、CAS、synchronized原理
1.常见的锁策略 (1)乐观锁 和 悲观锁 乐观锁:预测锁竞争的情况不激烈(工作量较少) 悲观锁:预测锁竞争的情况很激烈(工作量较多) (2)轻量级锁 和 重量级锁 轻量级锁:加锁和解锁的开销较小,效 ...
最新文章
- 【SeeMusic】视频编辑 ( 视频 X 坐标 | 视频 Y 坐标 | 视频旋转 | 视频扭曲 )
- 深入理解分布式技术 - 如何确保高可用
- CIDR地址块及其子网划分
- C#动态生成Word文档并填充数据(一)
- 逐步实现智慧人居,AIoT 是如何做到的?
- 解码H264文件的一些基础知识
- 谷歌云服务器账号,免费使用谷歌云服务器一年
- 第138天,我成为了CSDN博客专家,在搬砖的道路上继续努力
- 使用Python从头实现一个神经网络
- Mr. Kitayuta vs. Bamboos[二分+贪心][图像分析]
- 遇见你,是最美的意外
- linux 终端隐藏光标,如何在gnome-terminal中禁用闪烁的光标?
- Eclipse安装( jdk安装以及环境配置教程 )
- 线性回归梯度下降py实现
- “打开方式”中找不到打开某一类型文件想使用的软件
- 生日快乐python编程代码_如何用C语言编写一个很炫的生日快乐的程序?
- Andorid实例,淘宝评分条,星级评分条应用
- Concept Whitening(for Interpretable Image Recognition)
- 计算七参数,原来是这么操作的,一学就会!
- BIOS设置图解教程 Award Bios最新(转)
热门文章
- Leftist Heaps 习题解
- pillow图像格式转化和缩放操作
- Google 镜像站
- java md5加密与解密_Java——MD5加密与解密
- 人物简介——奥古斯塔·德摩根
- 数学建模番外篇1:PPT绘制3D图形
- html顺序播放mp3,完美:按顺序播放mp3的方法是什么?如何更改U盘中歌曲的播放顺序...
- java数据类型之间的转换_Java数据类型之间的转换(转)
- 怎样设计访谈提纲_如何设计调查问卷与访谈提纲要点分析.ppt
- 公众号html5页面代码,微信公众号网页H5跳转微信小程序