目录

  • `synchronized` 锁的是什么
    • 同步方法(非静态)测试示例一
    • 同步方法(非静态)测试示例二
    • 同步方法(静态)测试示例二
    • 同步代码块测试示例一
    • 同步代码块测试示例二
    • 同步代码块测试示例三
  • `synchronized` 锁总结
    • 同步方法
    • 同步代码块
  • `synchronized` 原理
  • `synchronized` 优化升级
    • 偏向锁
      • 偏向锁升级为轻量级锁
      • 偏向锁的释放
    • 轻量级锁
      • 轻量级锁升级为重量级锁
  • 锁粗化

synchronized 锁的是什么

在一些业务简单或某些单机系统不要求高性能高效率问题的情况下,可以使用 synchronized 进行线程安全处理,那么 synchronized 它到底锁的是什么

同步方法(非静态)测试示例一

public class SynchronizedTest {public synchronized void test() {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}public static void main(String[] args) {for (int i = 0; i < 3; i++) {new Thread(() -> {SynchronizedTest synchronizedTest = new SynchronizedTest();synchronizedTest.test();}).start();}}
}结果:
test 方法开始调用...
test 方法开始调用...
test 方法开始调用...
test 方法结束调用...
test 方法结束调用...
test 方法结束调用...
  • 上面的程序起动了三个线程,同时运行 SynchronizedTest 类中的 test() 方法,虽然 test() 方法加上了 synchronized,但是查看结果,貌似 synchronized 没起作用
  • 很明显,在 for 循环下面,我们创建了 3SynchronizedTest 实例。所以我们怀疑 synchronized 没起作用,与这 3SynchronizedTest 实例有关

同步方法(非静态)测试示例二

public class SynchronizedTest {public synchronized void test() {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}public static void main(String[] args) {SynchronizedTest synchronizedTest = new SynchronizedTest();for (int i = 0; i < 3; i++) {new Thread(synchronizedTest::test).start();}}
}结果:
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
  • 很明显,这一次在 for 循环下面,我们创建了 1SynchronizedTest 实例,而且 synchronized 起作用了。达到了预期效果
  • 结论:在非静态的方法上,加上 synchronized 时,它锁得是方法所在类的实例

同步方法(静态)测试示例二

public class SynchronizedTest {public static synchronized void test() {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}public static void main(String[] args) {for (int i = 0; i < 3; i++) {new Thread(SynchronizedTest::test).start();}}
}结果:
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
  • 在一个静态的方法上,加上 synchronized 时,synchronized 起作用了。达到了预期效果
  • 结论:在静态的方法上,加上 synchronized 时,它锁得是静态方法所在类的 Class 对象

同步代码块测试示例一

public class SynchronizedTest {public synchronized void test() {synchronized (this) {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}}public static void main(String[] args) throws ClassNotFoundException {for (int i = 0; i < 3; i++) {SynchronizedTest synchronizedTest = new SynchronizedTest();new Thread(synchronizedTest::test).start();}}
}结果:
test 方法开始调用...
test 方法开始调用...
test 方法开始调用...
test 方法结束调用...
test 方法结束调用...
test 方法结束调用...
  • 上面的程序起动了三个线程,同时运行 SynchronizedTest 类中的 test() 方法,虽然 test() 方法加上了 synchronized,但是查看结果,貌似 synchronized 没起作用
  • 很明显,在 for 循环下面,我们创建了 3SynchronizedTest 实例。所以我们怀疑 synchronized 没起作用,与这 3SynchronizedTest 实例有关

同步代码块测试示例二

public class SynchronizedTest {public synchronized void test() {synchronized (this) {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}}public static void main(String[] args) throws ClassNotFoundException {SynchronizedTest synchronizedTest = new SynchronizedTest();for (int i = 0; i < 3; i++) {new Thread(synchronizedTest::test).start();}}
}结果:
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
test 方法开始调用...
test 方法结束调用...
  • 在使用 synchronized (this) { } 同步代码块起时作用了。达到了预期效果
  • 结论:在使用 synchronized (this) { } 同步代码块时,它锁得是当前所在类的实例

同步代码块测试示例三

public class SynchronizedTest {public synchronized void test() {synchronized (SynchronizedTest.class) {System.out.println("test 方法开始调用...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("test 方法结束调用...");}}public static void main(String[] args) throws ClassNotFoundException {for (int i = 0; i < 3; i++) {SynchronizedTest synchronizedTest = new SynchronizedTest();new Thread(synchronizedTest::test).start();}}
}
  • 在使用 synchronized (类.class) { } 同步代码块起时作用了。达到了预期效果
  • 虽然在 for 循环下面,我们创建了 3SynchronizedTest 实例,显然没有干扰到预期效果,说明与这 3SynchronizedTest 实例无关
  • 结论:在使用 synchronized (类.class) { } 同步代码块时,它锁得是当前所在类的 Class 对象

synchronized 锁总结

同步方法

  • 在非静态的方法上,加上 synchronized 时,它锁得是方法所在类的实例
  • 在静态的方法上,加上 synchronized 时,它锁得是静态方法所在类的 Class 对象

同步代码块

  • 在使用 synchronized (this) { } 同步代码块时,它锁得是当前所在类的实例
  • 在使用 synchronized (类.class) { } 同步代码块时,它锁得是当前所在类的 Class 对象

synchronized 原理

JVM 中的同步是基于进入和退出 Monitor 监视器对象实现的。每个对象实例都会有一个 MonitorMonitor 可以和对象一起创建、销毁

  • 当多个线程同时访问一段同步代码时,多个线程会先被存放在 EntryList 集合(阻塞队列)中,处于 BLOCKED 阻塞状态的线程,都会被加入到该队列中
  • 接下来当线程获取到对象的 Monitor 监视器时,Monitor 监视器是依靠底层操作系统的 Mutex Lock 来实现互斥的,线程申请 Mutex 成功,则持有该 Mutex,其它线程将无法获取到该 Mutex
  • 如果线程调用 wait() 方法,就会释放当前持有的 Mutex,并且该线程会进入WaitSet 集合(等待队列)中,等待下一次被唤醒。此时线程会处于 WAITING 等待状态或者 TIMEDWAITING 超时等待状态
  • 如果当前线程顺利执行完方法,也将释放 Mutex

总的来说,就是同步锁在这种实现方式中,因 Monitor 监视器是依赖于底层的操作系统实现的,存在用户态与内核态之间的切换(可以理解为上下文切换),所以增加了性能开销

synchronized 优化升级

为了提升性能,jdk 1.6 引入了偏向锁、轻量级锁、重量级锁,来减少锁竞争带来的线程上下文切换

  • jdk 1.6 中,锁对象一共有 4 种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁,这几个状态会随着竞争情况逐渐升级
  • 锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率

偏向锁

经过 HotSpot 的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁

偏向锁升级为轻量级锁

  • 当线程 A 访问同步代码块并获取锁对象时,会在 Java 对象头和栈帧中记录偏向锁的线程 ID,因为偏向锁不会主动释放锁,因此以后线程 A 再次获取锁的时候,需要比较当前线程的线程 IDJava 对象头中的线程 ID 是否一致
  • 如果一致,说明还是线程 A 获取锁对象,则无需使用 CAS 来加锁、解锁
  • 如果不一致,说明线程 B 要竞争锁对象,而偏向锁不会主动释放,因此还是存储的线程 A 的线程 ID,那么需要查看 Java 对象头中记录的线程 A 是否存活
  • 如果没有存活,那么锁对象被重置为无锁状态,线程 B 可以竞争将其设置为偏向锁**
  • 如果存活,那么立刻查找线程 A 的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程 A,撤销偏向锁,升级为轻量级锁

偏向锁的释放

偏向锁的释放采用只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争,然后其他线程使用 CAS 替换掉原来的线程的线程 ID

轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要 CPU 从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋着等待锁释放

轻量级锁升级为重量级锁

  • 线程 A 获取轻量级锁时,会先把锁对象的对象头 MarkWord 复制一份到线程 A 的栈帧中创建用于存储锁记录的空间,然后使用 CAS 把锁对象的对象头中的内容替换为线程 A 存储的锁记录的地址
  • 如果在线程 A 复制对象头的同时(在线程 BCAS 之前),线程 B 也准备获取锁,复制了对象头到线程 B 的锁记录空间中,但是在线程 BCAS 的时候,发现线程 A 已经把锁对象的对象头换了,线程 BCAS 失败,那么此时线程 B 就尝试使用自旋锁来等待线程 A 释放锁
  • 但是如果自旋的时间太长也不行,因为自旋是要消耗 CPU 的,因此自旋的次数是有限制的,如果自旋次数到了线程 A 还没有释放锁,或者线程 A 还在执行,线程 B 还在自旋等待,这时又有一个线程 C 过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止 CPU 空转

为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态

锁粗化

按理来说,同步块的作用范围应该尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁

但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗

锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作

关于synchronized相关推荐

  1. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  2. java static 可见性_Java多线程 synchronized与可见性的关系以及可见性问题总结

    作者:七里香的编程之路 出自:OSCHINA 原文:my.oschina.net/u/4098550/blog/4548274 能保证可见性的措施 除了volatile 可以让变量保证可见性外.hap ...

  3. 你真的掌握了并发编程volatile synchronized么?

    先看代码: import java.util.concurrent.atomic.AtomicInteger;/**** @author xialuomantian*/ public class Ne ...

  4. Java使用字节码和汇编语言同步分析volatile,synchronized的底层实现

    关于怎么查看字节码的五种方法参考本人另一篇文章<Java以及IDEA下查看字节码的五种方法> 查看汇编语言汇编码 说要看汇编还是很有必要的,因为有些地方比如加锁其实还是通过汇编实现的,只看 ...

  5. java并发vol_java 并发中 volitile、synchronized和lock的比较(一)

    1.volitile和(synchronnized.lock) 首先比较volitile和synchronnized,volitile线程不安全,但是synchronized则是线程安全的. voli ...

  6. synchronized底层原理_你用过synchronized吗?它的底层原理是什么?Java经典面试题来了...

    并发编程已经成为程序员必备技能 作为Java程序员,不懂得并发编程显然已经不能满足市场需求了,尤其是在面试过程中将处于被动地位,也有可能面试将就此终结. 那么作为Java开发者的你,日常虽然可以基于J ...

  7. 面试题-自旋锁,以及jvm对synchronized的优化

    背景 想要弄清楚这些问题,需要弄清楚其他的很多问题. 比如,对象,而对象本身又可以延伸出很多其他的问题. 我们平时不过只是在使用对象而已,怎么使用?就是new 对象.这只是语法层面的使用,相当于会了一 ...

  8. JAVA多线程之Synchronized、wait、notify实例讲解

    一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...

  9. ReentrantLock与synchronized

    1.ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的 ...

  10. Java并发之synchronized

    synchronized关键字最主要有以下3种应用方式 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁:实例锁,一个实例一把锁 修饰静态方法,作用于当前类对象加锁,进入同步代码前要 ...

最新文章

  1. 钉钉需要什么java知识_Java钉钉开发_01_开发前的准备
  2. BarTender安装常见问题集结
  3. 十条nmap常用的扫描命令
  4. RabbitMQ(三) ——发布订阅
  5. python 工资管理软件_4_python之路之模拟工资管理系统
  6. c语言定时器作用,Go语言定时器实现原理及作用
  7. div里面放ul,使ul横向和纵向滚动
  8. JSP的自定义标签(三)之带标签体的标签
  9. 用glew,glfw实现opengl绘制3D学习笔记1-实现一个窗口
  10. 服务器的mdf文件怎么打开,mdf文件如何打开 mdf文件打开操作步骤
  11. web批量打印pdf
  12. java access_Java 连接Access数据库的两种方式
  13. Nginx安全配置手册
  14. 加密编码类型的密文特征分析
  15. 【丢不掉的爱好-Android01】兜兜转转
  16. 中南大学计算机博士就业,求救!中南大学博士毕业要求
  17. java.lang.NoClassDefFoundError: org/apache/commons/pool2/PooledObjectFactory
  18. 自动化构建部署(CICD)
  19. Excel如何批量将中文名字翻译为英文
  20. win7 dll怎么在xp运行_微信DLL劫持反弹shell复现

热门文章

  1. CNN卷积层里的多输入多输出通道channel 动手学深度学习v2 pytorch
  2. 翻译: 图解卡尔曼滤波器的工作原理
  3. 容器技术Docker K8s 41 Serverless Kubernetes(ASK)详解-ASK集群管理
  4. fill()函数和fill_n()函数
  5. php颜色淡入代码,JavaScript_jquery 淡入淡出效果的简单实现,样式:复制代码 代码如下:nbsp - phpStudy...
  6. php循环5000条会不会崩,PHP -- 循环
  7. 串口屏与6050_MPU6050 STM32控制 六轴传感器,可通过串口屏显示,还可连接匿名上位机 欧拉角 SCM 单片 发 267万源代码下载- www.pudn.com...
  8. 最新关于高德地图定位失败10:定位服务启动、解决办法
  9. 计算机组成原理完整学习笔记(四):输入输出系统
  10. 【HDU 5033】【经典单调栈问题】Building