synchronized是掌握Java高并发的关键知识点,底层源码更是面试重灾区。本文从源码学习synchronized的原理,讲解对象头、偏向锁、轻量级锁、重量级锁等概念。
扫码关注《Java学研大本营》
Java对象头原理
对象头就和它名字一样,描述的就是JVM层面一个Java对象的头部信息。下面请看源码。
class oopDesc { // 这个类就是所有对象头部的基类
private:
/* 标记字段,其实就是markOopDesc,只不过这里用了typedef类型定义,
源码:typedef class markOopDesc* markOop; */
volatile markOop _mark;
union _metadata { // 指向对象所属class对象指针
Klass* _klass;
narrowKlass _compressed_klass; // 开启指针压缩后,指向压缩后的class对象
} _metadata;
}
class markOopDesc: public oopDesc { // 描述了标记位,其实就是带标记的oop对象
private:
uintptr_t value() const { return (uintptr_t) this; }
public:
// 定义了一些枚举常量,注意,常量都是以位bit为单位
enum { age_bits = 4, // 对象年龄4位
lock_bits = 2, // 锁标记2位
biased_lock_bits = 1, // 偏向锁1位
// 最大hash值位数 = CPU位数 - 年龄位数 - 锁位数 - 偏向锁位数
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits, // 最大值为31位
cms_bits = LP64_ONLY(1) NOT_LP64(0), // CMS占用位数,根据CPU位数选择1或0
epoch_bits = 2 // 用于支持偏向锁2位
}
以上两个类oopDesc和markOopDesc共同描述了一个对象头部的样子,对象数据(32位或64位),指向对象所属klass对象指针。对象数据的所有位按照功能切割成不同位数,并且markOopDesc对每个位数提供了详细描述,给出了这些位分别在所有位中的位置(通过shift偏移量)。这里我们还是通过全局描述的方式来给读者直观展示在32位机和64位机中,对象数据的不同表现,以及锁状态位的不同状态。
对象头部bit描述 (采用大端序放置):
32 bits: // 在32位平台中
--------
// 正常对象:25位hashCode,4位年龄位,偏向锁1位,2位锁标记
hash:25 ------------>| age:4 biased_lock:1 lock:2
/* 已经被偏向锁锁定的对象:23位指向JavaThread线程对象的指针,2位偏向锁epoch,4位年龄,偏向锁1位,2位锁标记 */
JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2
// CMS 释放空闲块
size:32 ------------------------------------------>|
// CMS 晋升对象:29位转移后对象指针,3位晋升标志位
PromotedObject*:29 ---------->| promo_bits:3 ----->|
64 bits:// 在64 位平台中
--------
// 正常对象:25位未使用,1位未使用,31 位hashCode,4位年龄位,偏向锁1位,2位锁标记
unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2
/* 已经被偏向锁锁定的对象:54位指向JavaThread线程对象的指针,2位偏向锁epoch,4位年龄,偏向锁1位,2位锁标记 */
JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2
// CMS 晋升对象:61位对象指针,3位晋升标志位
PromotedObject*:61 --------------------->| promo_bits:3 ----->|
// CMS 释放空闲块
size:64 ----------------------------------------------------->|
// CMS状态下的普通对象
unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2
// CMS状态下的偏向锁对象
JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2
// CMS状态下的晋升对象
narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->|
// CMS状态下的空闲对象
unused:21 size:35 -->| cms_free:1 unused:7 ------------------>|
// - hash包含唯一值且最大值为31位,在64位机中hash值也不会超过32位,因为它们不适合生成一个掩码。
// - 偏向锁模式被使用于偏向一个线程,当偏向锁被设置在低3位时,锁要么偏向一个线程或者是匿名偏向,表明它有可能是被偏向状态。被偏向线程在锁定和解锁时可以在不使用原子操作的情况下由该线程执行。当偏向锁被撤销时,它会恢复到下面的正常状态描述的锁定方案。
[JavaThread* | epoch | age | 1 | 01] // 偏向锁偏向于一个线程,JavaThread*指向线程ID
[0 | epoch | age | 1 | 01] // 匿名偏向,即开启了偏向锁但是还没有被线程获取偏向锁
[ptr | 00] locked // ptr指针指向真实线程栈上的对象头
[header | 0 | 01] unlocked // 普通对象头
[ptr | 10] monitor // 膨胀锁,ptr指向监视器对象
[ptr | 11] marked // 由markSweep用于标记对象,在其他时间无效
读者大致看看对象头的描述即可,在了解了对象头之后,我们可以在下面的monitorenter和monitorexit 操作中看到,都是对于对象头部的这些信息进行灵活运用。
_monitorenter 获取锁过程原理
首先我们看一段最基本的Java通过synchronized关键字上锁的代码。
public class Demo{
static Object lock = new Object();
static int counter = 1;
public static void main(String[] args){
// 通过对lock对象上锁来保证代码块的操作的线程安全性
synchronized(lock){
counter++;
}
}
}
上面的代码很容易理解,我们直接来看编译的字节码。
0: getstatic #2 // Field lock:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter // 同步代码块开始
6: getstatic #3 // Field counter:I
9: iconst_1
10: iadd
11: putstatic #3 // Field counter:I
14: aload_1
15: monitorexit // 同步代码块结束
/* 发生异常后,跳转到第19行代码。不管是否发生异常都会释放锁对象。这里,如果没有发生异常,则直接跳转到24行,从main方法中返回。*/
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
从以上代码中可以看到,上锁过程是通过monitorenter字节码指令来操作的,这里直接跳到monitorenter的源码。
CASE(_monitorenter): {
// 获取锁对象
oop lockee = STACK_OBJECT(-1);
// 在线程栈上找到一个空闲的BasicObjectLock对象
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
if (entry != NULL) {
// 保存锁对象,表明当前BasicObjectLock持有锁对象lockee
entry->set_obj(lockee);
int success = false;
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
markOop mark = lockee->mark(); // 获取锁对象的头部标记信息
// 获取没有hash值的标记位值,这里为0
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// 判断使用了偏向锁
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread(); // 获取线程id
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
/* anticipated_bias_locking_value为0,表明还没有批量撤销偏向锁,且当前线程
持有了偏向锁,直接退出 */
if (anticipated_bias_locking_value == 0) {
// already biased towards this thread, nothing to do
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
else if ((anticipated_bias_locking_value &
markOopDesc::biased_lock_mask_in_place) != 0) {
/* anticipated_bias_locking_value不为0,可能是批量撤销偏向锁,需要继续判断是否有
线程持有偏向锁,如果其他线程持有偏向锁,判定发生了冲突,就需要撤销偏向锁 */
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// CAS将对象头从mark替换为header撤销偏向锁
if (lockee->cas_set_mark(header, mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
/* 如果anticipated_bias_locking_value不为0,在批量撤销偏向锁时需要更改
epoch的值,这里如果epoch改变了,当前线程需要重偏向 */
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
// CAS重偏向
if (lockee->cas_set_mark(new_header, mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
// CAS失败,发生了竞争,那么进入monitorenter
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
/* 以上条件均不满足,表明开启了偏向锁,此时偏向锁状态为匿名偏向,尝试CAS
将其偏向为当前线程*/
markOop header = (markOop) ((uintptr_t) mark &
((uintptr_t)markOopDesc::biased_lock_mask_in_place |
(uintptr_t)markOopDesc::age_mask_in_place |
epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// CAS重偏向
if (lockee->cas_set_mark(new_header, header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// CAS失败,发生了竞争,那么进入monitorenter
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry),
handle_exception);
}
success = true;
}
}
// 没有获取到锁,那么进入传统的轻量级锁
if (!success) {
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors; // 判断是否直接使用重量级锁
/* 如果没有指定直接使用重量级锁,那么通过CAS操作尝试获取轻量级锁,即替换
头部指针,指向entry */
if (call_vm || lockee->cas_set_mark((markOop)entry, displaced) != displaced) {
// 如果失败,可能是当前线程轻量级锁重入,那么判断是否是锁重入
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits()))
{
// 轻量级锁重入,不需要设置displaced_header信息
entry->lock()->set_displaced_header(NULL);
} else {