synchronized
synchronized实现
同步的基
础:
对于普通同步方法,锁
是当前
实
例
对象。
对于静
态
同步方法,
锁
是当前
类
的
Class
对象。
对于同步方法
块
,
锁
是
Synchonized
括号里配置的
对象。
对象头
synchronized
用的
锁
是存在
Java
对
象
头里的。如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit,如图:
Java
对
象
头
里的
Mark Word
里默
认
存
储对
象的
HashCode
、分代年
龄
和
锁标记
位。
32
位
JVM 的Mark Word
的默
认
存
储结
构如下图所示
在运行期
间
,
Mark Word
里存
储
的数据会随着
锁标
志位的
变
化而
变
化。
Mark Word
可能
变化为
存
储
以下
4
种数据,如下图所示
锁升级
由于JDK1.6 之前,synchronized实现锁机制为重量级锁,线程的切换涉及到用户态和内核态的切换,效率较低,所以JDK1.6 提出了锁升级的概念。
为
了减少
获
得
锁
和
释
放
锁带
来的性能消耗,引入了
“
偏向
锁
”
和
“
轻
量
级锁
”
,在 Java SE 1.6中,
锁
一共有
4
种状
态
,
级别
从低到高依次是:无
锁
状
态
、偏向
锁
状
态
、
轻
量
级锁
状态和重量
级锁
状
态
,
这
几个状
态
会随着
竞
争情况逐
渐
升级,但是锁
可以升
级
但不能降
级。
偏向锁
当一个
线
程
访问
同步
块
并获取
锁时
,会在
对
象
头
和
栈帧
中的
锁记录
里存
储锁
偏向的
线
程
ID
,以后
该线
程在
进
入和退出同步块时
不需要
进
行
CAS
操作来加
锁
和解
锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
轻量级锁
线
程在
执
行同步
块
之前,
JVM
会先在当前
线
程的
栈桢
中
创
建用于存
储锁记录
的空
间
,并将对
象
头
中的
Mark Word
复制到
锁记录
中,
然后
线
程
尝试
使用CAS将
对
象
头
中的
Mark Word
替
换为
指向
锁记录
的指
针
。如果成功,当前
线
程
获
得
锁
,如果失败,表示其他
线
程
竞
争
锁
,当前
线
程便
尝试
使用自旋来
获
取
锁
。
重量级锁
如果自旋获取锁失败,资源对象锁膨胀为重量级锁同时将锁状态修改为10。然后将该线程阻塞。Jvm恢复拥有资源锁的线程并继续执行同步代码块。因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。
锁升级过程是一个动态的过程,根据线程竞争的情况进行自动切换。在锁升级过程中,JVM会评估竞争情况,根据具体情况选择合适的锁类型,以提高程序的并发性能。