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引入的轻量级锁。

轻量级锁的获取流程:

  1. 首先判断当前对象是否处于一个无锁的状态,如果是,Java虚拟机将在当前线程的栈帧建立一个锁记录(Lock Record),用于存储对象目前的Mark Word的拷贝,如图所示。

  2. 将对象的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原理相关推荐

  1. 打工人,从 JMM 透析 volatile 与 synchronized 原理

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 在面试.并发编程.一些开源框架中总是会遇到 volatil ...

  2. StringBuffer、StringBuilder区别以及Synchronized原理

    1.为什么StringBuffer是线程安全的StringBuilder是线程不安全的 这里我只是列举了几个方法来对比,其他方法对比可以查看两个类的源码,从上面的截图可以看到StringBuffer实 ...

  3. 并发编程之Synchronized原理

    一.基本使用    1.Synchronized的作用. 原子性:确保线程互斥的访问同步代码: 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 "对一个变量unloc ...

  4. Java 并发编程CAS、volatile、synchronized原理详解

    CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...

  5. 【重难点】【JUC 04】synchronized 原理、ReentrantLock 原理、synchronized 和 Lock 的对比、CAS 无锁原理

    [重难点][JUC 04]synchronized 原理.ReentrantLock 原理.synchronized 和 Lock 的对比.CAS 无锁原理 文章目录 [重难点][JUC 04]syn ...

  6. 从 JMM 透析 volatile 与 synchronized 原理

    作者 | 李健青 来源 | 码哥字节(ID:MageByte) 头图 |  CSDN 下载自东方IC 在面试.并发编程.一些开源框架中总是会遇到 volatile 与 synchronized .sy ...

  7. Synchronized原理(偏向锁篇)

    Synchronized原理(偏向锁篇) 传统的锁机制 传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换.进程的上 ...

  8. java虚拟机线程调优与底层原理分析_啃碎并发(七):深入分析Synchronized原理...

    原标题:啃碎并发(七):深入分析Synchronized原理 前言 记得开始学习Java的时候,一遇到多线程情况就使用synchronized,相对于当时的我们来说synchronized是这么的神奇 ...

  9. 锁,CAS,Synchronized 原理

    作者:~小明学编程 文章专栏:JavaEE 格言:热爱编程的,终将被编程所厚爱. 目录 常见的锁 悲观锁与乐观锁 悲观锁 乐观锁 读写锁 重量级锁 vs 轻量级锁 挂起等待锁和自旋锁 公平锁和非公平锁 ...

  10. 锁策略、CAS、synchronized原理

    1.常见的锁策略 (1)乐观锁 和 悲观锁 乐观锁:预测锁竞争的情况不激烈(工作量较少) 悲观锁:预测锁竞争的情况很激烈(工作量较多) (2)轻量级锁 和 重量级锁 轻量级锁:加锁和解锁的开销较小,效 ...

最新文章

  1. 【SeeMusic】视频编辑 ( 视频 X 坐标 | 视频 Y 坐标 | 视频旋转 | 视频扭曲 )
  2. 深入理解分布式技术 - 如何确保高可用
  3. CIDR地址块及其子网划分
  4. C#动态生成Word文档并填充数据(一)
  5. 逐步实现智慧人居,AIoT 是如何做到的?
  6. 解码H264文件的一些基础知识
  7. 谷歌云服务器账号,免费使用谷歌云服务器一年
  8. 第138天,我成为了CSDN博客专家,在搬砖的道路上继续努力
  9. 使用Python从头实现一个神经网络
  10. Mr. Kitayuta vs. Bamboos[二分+贪心][图像分析]
  11. 遇见你,是最美的意外
  12. linux 终端隐藏光标,如何在gnome-terminal中禁用闪烁的光标?
  13. Eclipse安装( jdk安装以及环境配置教程 )
  14. 线性回归梯度下降py实现
  15. “打开方式”中找不到打开某一类型文件想使用的软件
  16. 生日快乐python编程代码_如何用C语言编写一个很炫的生日快乐的程序?
  17. Andorid实例,淘宝评分条,星级评分条应用
  18. Concept Whitening(for Interpretable Image Recognition)
  19. 计算七参数,原来是这么操作的,一学就会!
  20. BIOS设置图解教程 Award Bios最新(转)

热门文章

  1. Leftist Heaps 习题解
  2. pillow图像格式转化和缩放操作
  3. Google 镜像站
  4. java md5加密与解密_Java——MD5加密与解密
  5. 人物简介——奥古斯塔·德摩根
  6. 数学建模番外篇1:PPT绘制3D图形
  7. html顺序播放mp3,完美:按顺序播放mp3的方法是什么?如何更改U盘中歌曲的播放顺序...
  8. java数据类型之间的转换_Java数据类型之间的转换(转)
  9. 怎样设计访谈提纲_如何设计调查问卷与访谈提纲要点分析.ppt
  10. 公众号html5页面代码,微信公众号网页H5跳转微信小程序