开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
Synchronized
简介
我们通常使用synchronized关键字来给一段代码或一个方法上锁。它通常有以下三种形式:
// 关键字在实例方法上,锁为当前实例
public synchronized void instanceLock() {
// code
}
// 关键字在静态方法上,锁为当前Class对象
public static synchronized void classLock() {
// code
}
// 关键字在代码块上,锁为括号里面的对象
public void blockLock() {
Object o = new Object();
synchronized (o) {
// code
}
}
我们这里介绍一下“临界区”的概念。所谓“临界区”,指的是某一块代码区域,它同一时刻只能由一个线程执行。在上面的例子中,如果synchronized关键字在方法上,那临界区就是整个方法内部。而如果是使用synchronized代码块,那临界区就指的是代码块内部的区域。
同理,下面这两个方法也应该是等价的:
// 关键字在静态方法上,锁为当前Class对象
public static synchronized void classLock() {
// code
}
// 关键字在代码块上,锁为括号里面的对象
public void blockLock() {
synchronized (this.getClass()) {
// code
}
}
锁
一个对象其实有四种锁状态,它们级别由低到高依次是:
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
每个Java对象都有对象头。如果是非数组类型,则用2个字宽来存储对象头,如果是数组,则会用3个字宽来存储对象头。在32位处理器中,一个字宽是32位;在64位虚拟机中,一个字宽是64位。对象头的内容如下表:
| 长度 | 内容 | 说明 |
|---|---|---|
| 32/64bit | Mark Word | 存储对象的hashCode或锁信息等 |
| 32/64bit | Class Metadata Address | 存储到对象类型数据的指针 |
| 32/64bit | Array length | 数组的长度(如果是数组) |
我们主要来看看Mark Word的格式:
| 锁状态 | 29 bit 或 61 bit | 1 bit 是否是偏向锁? | 2 bit 锁标志位 |
|---|---|---|---|
| 无锁 | 0 | 01 | |
| 偏向锁 | 线程ID | 1 | 01 |
| 轻量级锁 | 指向栈中锁记录的指针 | 此时这一位不用于标识偏向锁 | 00 |
| 重量级锁 | 指向互斥量(重量级锁)的指针 | 此时这一位不用于标识偏向锁 | 10 |
| GC标记 | 此时这一位不用于标识偏向锁 | 11 |
可以看到,当对象状态为偏向锁时,Mark Word存储的是偏向的线程ID;当状态为轻量级锁时,Mark Word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁时,Mark Word为指向堆中的monitor对象的指针。