Java 知识 -- 锁
本文最后更新于:3 天前
-
ReentrantLock
大部分 JDK 级别加锁+CAS(CPU指令,非常快)
只有在资源竞争,加锁失败的情况下,release CPU,才会阻塞线程
非公平
-
synchronize
-
JDK 1.6 以前直接更改操作系统 -> JDK 1.6 以后,首先通过对象头里面存储锁状态标识,升级为重量锁之后才调用操作系统代码
-
加锁过程:
-
在代码进入同步块的时候,如果同步对象锁状态为无锁状态 (
101
)(锁标志位为“01”状态,能否偏向为“1”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。 -
拷贝对象头中的Mark Word复制到锁记录中。
-
拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。
-
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。
-
如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
-
偏向锁会记录当前线程 id
但是轻量锁不会记录
-
偏向锁膨胀为轻量锁的条件:
-
001 不可能偏向 -> 000 升级为轻量锁
-
线程交替运行
-
-
锁的膨胀一般是不可逆的
可逆和epoch有关
对象锁和类锁是不一样的
对象在堆上,类信息则在方法区
所以 new 多个类,多个实例同时访问各自的对象锁就无效
例如:
public synchronized (this)
但是如果是类锁就不是这样的,
-
对象头
| 锁 | 62位 |锁状态| | 过程 |锁状态|
| 无锁可偏向 | unused:57 | u1 | age:4 | 1 | 01 | klass word (64,如果开启了指针压缩就是32) | 默认初始状态 | 101 |
| 无锁不可偏向 | unused: 25 | hashcode:31 | u1 | age:4 | 0 | 01 | klass word | 计算了hashcode,保存了hashcode,但是没有地方存偏向锁的线程id了 | 001 |
| 有偏向锁,已经偏向 | thread(线程id): 54| epoch:2 | u1 | age:4 | 1 | 01 | klass word | 默认状态到偏向锁状态,没有计算hashcode | 101 |
| 轻量锁 | ptr_to_lock_record(指向栈中锁记录的指针):62 | 00 | klass word | 线程交替执行,或者由无锁不可偏向状态升级而来 | 000 |
| 重量锁 | ptr_to_heavyweight_monitor(指向重量锁记录的指针):62 | 10 | klass word | 轻量锁抢锁失败,自旋锁自旋一段时间,如果还是抢锁失败则会变成重量锁 | ?10 |
本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 。转载请注明出处!