基本使用
- synchronized放在实例方法上,锁对象是当前的this对象
- synchronized放在类方法(静态方法)上,锁对象是方法区中的类对象
- synchronized修饰代码块,也就是synchronized(object){},锁对象是()中的对象
synchronized
用来修饰方法时,是通过 ACC_SYNCHRONIZED 标识符来保持线程同步的。
synchronized
用来修饰代码块时,是通过 monitorenter 和 monitorexit 指令来完成
实现原理
- 代码层面 , Synchronized 关键字。
- JAVA 字节码层面 ,同步方法是 ACC_SYNCHRONIZED 修饰的方法定义,同步代码块使用 monitorenter 和 monitorexit 两个指令实现。
- JVM 层面 , 无锁,偏向锁,轻量级锁,重量级锁。
- 操作系统层面 是调用了汇编指令 lock cmpxchg,通过 lock 保证后面的命令(cmpxchg)只能一个线程执行。
- 硬件层面 ,是锁住了一个北桥的信号量。
1) CPMXCHG
- 用于比较并交换操作数,CPU对CAS的原语支持
- 非原子性,最早用于单核CPU
2) LOCK前缀
- CPU保证被其修饰的指令的原子性
- 禁止重排序
- 缓存刷新到内存
借助什么来实现?
1. 对象头中的Mark Word
偏向锁
if (锁的标记位 == 01) {
if (偏向标记是1){
是偏向锁且可偏向
boolean CAS操作结果 = CAS操作替换偏向线程的ID为当前线程
if ( CAS操作结果 == 成功){
当前线程获得锁
执行同步代码块
} else {
CAS操作失败
开始【偏向锁的撤销】{
等到全局安全点
var 状态 = 检查原来持有锁的线程的状态
if (状态 == terminated || 已经退出同步代码区)
原线程释放锁
当前线程获得锁
else if (状态 == runnable && 未退出同步代码区){
执行【偏向锁膨胀到轻量级锁】的过程{
原持有锁的线程栈幁分配锁记录、替换MarkWord并指向对象地址、执行同步代码块、CAS操作释放锁
当前线程执行轻量级锁的抢锁过程{
CAS自旋
if (自旋一定次数还没有获取锁){
膨胀到重量级锁
}
}
}
}
}
}
}else {
goto line 4 执行CAS操作
}
}else {
不是偏向锁
}
轻量级锁
if (锁的标记位 == 00) {
是轻量级锁
执行轻量级锁的抢占{
当前线程的栈幁中 分配 【锁记录】,【锁记录】由两个部分构成,【displaced Markword】 和 【onwer指针】
把锁对象的【对象头】中的【Markword】拷贝到锁记录中的【displaced Markword】中
onwer指针 指向 该锁对象
CAS修改锁对象的对象头,使其中的【指向线程锁记录的指针】 这一字段指向当前线程
if (CAS操作成功){
当前线程持有锁
}else{
CAS操作失败
CAS自旋
if (自旋超过一定次数还没有成功){
升级为重量级锁{
改变Markword
挂起当前线程
}
}
}
}
}else {
不是轻量级锁
}
2. Monitor监视器对象
Java中,每个对象里面隐式的存在一个叫monitor(对象监视器)的对象,Monitor监视器对象存在于Java对象的对象头Mark Word中,这个对象源码是采用C++实现的
class ObjectMonitor {
...
ObjectMonitor() {
_header = NULL; //markOop object header
_count = 0;
_waiters = 0, //Number of waiting threads
_recursions = 0; //Thread reentry times
_object = NULL; //Store Monitor object
_owner = NULL; //Thread to get ObjectMonitor object
_WaitSet = NULL; //List of threads in wait state
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // One way list
FreeNext = NULL ;
_EntryList = NULL ; //Thread waiting for lock BLOCKED
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0; //ID of the previous owning thread of the monitor
}
...
流程
感谢:Jacksgong
锁重入
-
偏向锁的锁重入
- 简单判断mark word中的偏向线程ID
-
轻量级锁的锁重入
- 再次放入锁记录, 只不过displaed markword为null
- 通过锁记录的个数判断重入的次数
- 在高位的是第一次的锁记录
-
重量级锁的锁重入
- 操作系统来实现