【Java并发体系】synchronized 的使用与原理

基本语法

synchronized 有三种方式来加锁,不同的修饰类型,代表锁的控制粒度

  • 修饰实例方法,获取当前对象锁;
  • 修饰静态方法,获取当前类的Class对象锁;
  • 修饰代码块,获取指定对象锁;
// 修饰实例方法
private synchronized int nextThreadNum() {
   return 0;
}
// 修饰静态方法
private static synchronized int nextThreadNum() {
   return 0;
}
// 修饰代码块
private Object lockObj = new Object();
private int nextThreadNum() {
   synchronized(lockObj) {
		return 0;
   }
}

synchronized语义

  • synchronized可以保证方法或代码块在运行时,同一时刻只有一个线程可以进入到临界区,同时它还可以保证共享变量的内存可见性;
  • 每个对象都拥有自己的监视锁Monitor对象,java通过Monitor对象实现synchronized语义,保证了在同一时刻只有一个线程能访问共享资源。
Monitor对象
  • Monitor详细介绍
  • Monitor其实是一种同步工具,也可以说是一种同步机制
    • 提供线程进入临界区的许可;
    • 也提供了singal机制,允许正持有“许可”的线程暂时放弃“许可”,等待某个条件;并且条件成立后,拥有“许可”的线程可“通知”等待条件的线程重新获取“许可”

synchronized修饰的对象会成为锁对象,那么锁信息和锁状态是如何存储的,synchronized如何工作的呢?

锁存储

推荐一篇对于Mark word详解

任何一个对象都有可能被synchronized修饰,那么对象数据中必定需要预留空间存储其锁信息;(Mark Word)

  • 对象在内存中的存储布局
    • 对象头(Header)
      • Mark Word
      • 类元信息
    • 实例数据(Instance Data)
    • 对齐填充(Padding)
  • Mark word 记录了对象和锁有关的信息
    • 32bit 的Mark word 不同锁状态的存储内容
锁状态锁信息 30bit锁标志 2bit
无锁HashCode(25bit),分代年龄(4bit), 偏向锁(1bit)01
偏向锁线程ID(23bit),Epoch(2bit),分代年龄(4bit), 偏向锁(1bit)01
轻量级锁指向栈中锁记录的指针00
重量级锁指向重量级锁的指针10
GC标记11
  • 64bit 的Mark word 不同锁状态的存储内容
锁状态锁信息 62bit锁标志 2bit
无锁unused(25bit),HashCode(31bit),unused(1bit),分代年龄(4bit), 偏向锁(1bit)01
偏向锁线程ID(54bit),Epoch(2bit),unused(1bit),分代年龄(4bit), 偏向锁(1bit)01
轻量级锁指向栈中锁记录的指针00
重量级锁指向重量级锁的指针10
GC标记11
为什么任何对象都可以实现锁
  • 1 Java 中的每个对象都派生自 Object 类;
  • 2 线程在获取锁的时候,实际上就是获得一个监视器对象(monitor),而所有对象都有一个monitor对象与之对应;
synchronized 锁的升级
  • JDK1.5 都是重量级锁,1.6开始引入了偏向锁和轻量级锁
偏向锁的基本原理
  • 通过Mark word存储当前拥有偏向锁的线程,使得该线程进入、退出同步代码块时,无需额外的加锁和释放锁。
偏向锁的获取与撤销
  • 1 判断锁标志(01 则进入偏向锁获取)
  • 2 判断偏向锁标志(0:无锁,1:可能Thread为空、也可能是其他Thread、也有可能是当前线程)
  • 3 检查ThreadId是否当前线程
  • 4 CAS 获取偏向锁
  • 5 检查原持有偏向锁线程执行状态(需要等待原持有偏向锁线程进入全局安全点)
  • 注:当只有单线程访问没有出现同步块竞争时,偏向锁是不需要解锁的
    在这里插入图片描述
JVM设置偏向锁参数
  • jdk1.6之后默认开启
  • 参数开启方式:-XX:+UseBiasedLocking,启动默认五秒之后生效
  • 额外配置可立即生效,配置延迟时间为0:-XX:BiasedLockingStartupDelay=0
  • 关闭偏向锁配置:-XX:-UseBiasedLocking
轻量级锁的基本原理
  • 当处于偏向锁状态的同步对象产生第二个线程竞争时,偏向锁将升级为轻量级锁;
  • 持有偏向锁的线程将分配栈空间存储锁记录,同步对象的Mark word也CAS为栈中锁记录地址;
  • 进入竞争的第二个线程也分配锁记录的栈空间,将进入自旋状态CAS替换同步对象的Mark word(将Mark word替换成自己的栈中锁记录地址)
  • 栈空间锁记录存储同步对象的Mark word,锁记录owner指向同步对象
  • 默认情况下自旋的次数是 10 次,可进行自行设置次数 -XX:PreBlockSpin=n
轻量级锁的加锁和解锁

在这里插入图片描述

JVM设置轻量级锁参数
  • JDK1.6:-XX:+UseSpinning 开启自旋锁;-XX:PreBlockSpin参数来设置自旋锁等待的次数
  • JDK1.7以后:自旋锁的参数被取消,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整
重量级锁的基本原理
  • 当轻量级锁膨胀到重量级锁之后,线程只能被阻塞等待被唤醒;
  • 升级为重量级锁时,线程访问同步块是获取monitor对象和释放monitor对象的过程
    • 前面说到过,所有对象都拥有自己的monitor对象(监视器),JVM提供的同步机制
    • Monitor的线程阻塞等待机制是通过操作系统的mutex互斥锁实现的
      在这里插入图片描述

总结

synchronized 是可重入锁
  • 因为获取锁就是获取monitor对象,monitor对象内部维护线程重入次数,即重复获取同一个monitor对象无需重复繁重的加锁过程
synchronized 是非公平锁
  • 无论锁处于各种状态,线程尝试获取锁时,并不会先判断进入阻塞队列,而是直接尝试获取锁,不满足先到先得,所以 synchronized 是非公平锁
关键词列表
  • 使用方式
  • synchronized 语义
  • markword
  • 锁升级
  • monitor
  • 1.6对锁优化
    引入自旋锁,自适应自旋锁,偏向锁,锁消除,锁粗化,锁膨胀
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值