多线程 Part 2 - synchronized
1. synchronized 锁住了什么?
sychronized可以锁住的是对象实例或者类
- 在静态方法加锁,锁住类
public static synchronized void myfunction(){}
- 在普通方法加锁,锁住对象实例
public synchronized void myfunction(){}
- 方法中使用同步代码块
public void myfunction(){
//使用this,锁住的是this所代表的对象实例
synchronized(this){}
}
Object obj = new MyClass();
public void myfunction(){
//锁住obj实例
synchronized(obj){}
}
public void myfunction(){
//锁住类
synchronized(MyClass.Class){}
}
2. synchronized(obj){…} 发生了什么?
这里先不深入讨论synchronized底层原理,学扎实了再更新
一个Java对象由三个部分组成:
是否固定大小 | 是否一定存在 | |
---|---|---|
对象头 | 固定 | 一定 |
实例数据 | 不固定 | 不一定 |
数据对齐 | 不固定 | 不一定 |
- JVM规定对象实例的大小必须为8字节的整数倍,而数据对齐,就是填充使对象大小满足这个要求
- 当synchronized对某一个对象实例上锁的时候,实际上就是改变了这个对象的对象头
a). Java对象布局
导入 jol-core-0.9.jar,可以在这里下载找到
然后我们创建一个Demo类实例,来看一下它的对象布局
注意在Java 8下测试,Java 14环境下打印出来的对象头有点看不懂…
import org.openjdk.jol.info.ClassLayout;
//Demo类
class Demo
{
short id;
}
public class TestClassLayout {
public static void main(String[] args) {
//打印对象布局
System.out.println(ClassLayout.parseInstance(new Demo()).toPrintable());
}
}
输出:
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 2 short Demo.id 0
14 2 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total
通过输出可以看到,这个Demo对象实例大小一共是16 bytes,其中:
- 12 bytes的对象头
- 2 bytes的short变量数据
- 2 bytes的填充用于数据对齐
b). Java对象头的组成
Java对象头分为两部分:
- Mark Word
- klass pointer
c). Java对象头到底多大?
这里有三种情况:
1. 开启指针压缩
在Java 8环境下,是默认开启指针压缩的。这个时候在64位虚拟机下,Mark Word占64 bits,klass pointer占32 bits,对象头总共96 bits,就如之前那个Demo的例子。
2. 不开启指针压缩
这个时候,在64位虚拟机下,Mark Word占64 bits,klass pointer也占64 bits,对象头总共128 bits。
运行时设置不开启指针压缩,只要加入VM arguments: -XX:-UseCompressedOops
就可以了(开启指针压缩的命令是-XX:+UseCompressedOops
)
同样Demo的例子,输出:
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 68 08 10 fb (01101000 00001000 00010000 11111011) (-82835352)
12 4 (object header) 4d 02 00 00 (01001101 00000010 00000000 00000000) (589)
16 2 short Demo.id 0
18 6 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
这里明显可以看到对象头变成了16个字节,前8个是Mark Word,后8个是klass pointer
3. 数组对象
如果是数组对象的话,对象头就会再多出来32 bits放数组长度
import org.openjdk.jol.info.ClassLayout;
//查看一下长度1234的Int类型数组对象布局
public class TestObjectHeader {
public static void main(String[] args) {
int[] arr = new int[1234];
System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}
}
开启指针压缩的输出:
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 20 (01101101 00000001 00000000 00100000) (536871277)
12 4 (object header) d2 04 00 00 (11010010 00000100 00000000 00000000) (1234)
16 4936 int [I.<elements> N/A
Instance size: 4952 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
关闭指针压缩的输出:
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 68 0b d0 aa (01101000 00001011 11010000 10101010) (-1429206168)
12 4 (object header) 51 02 00 00 (01010001 00000010 00000000 00000000) (593)
16 4 (object header) d2 04 00 00 (11010010 00000100 00000000 00000000) (1234)
20 4 (alignment/padding gap)
24 4936 int [I.<elements> N/A
Instance size: 4960 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
可以看到对象头的最后4个字节都表示数组的长度。
开启指针压缩时,数组对象头有16个字节;而关闭指针压缩时,数组对象头有20个字节!
d). Java对象头的结构
|--------------------------------------------------------------------------------------------------------------|--------------------|
| Object Header (96 bits) | State |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| Mark Word (64 bits) | Klass Word (32 bits) | |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | cms_free:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 | epoch:2 | cms_free:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_lock_record | lock:2 | OOP to metadata object | Lightweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_heavyweight_monitor | lock:2 | OOP to metadata object | Heavyweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
根据 markOop.hpp 里面这段源码,我们可以这样根据Mark Word尾三位确定锁状态
enum {
locked_value = 0,
unlocked_value = 1,
monitor_value = 2,
marked_value = 3,
biased_lock_pattern = 5
};
锁状态 | biased_lock | lock |
---|---|---|
无锁 | 0 | 01 |
偏向锁 | 1 | 01 |
轻量级锁 | - | 00 |
重量级锁 | - | 10 |
GC标记 | - | 11 |
e). 测试锁状态
在测试之前先搞明白自己机器是大端序还是小端序,当然我们可以通过打印hashCode来搞清楚这件事
import org.openjdk.jol.info.ClassLayout;
class Demo{}
public class TestObjectHeader {
public static void main(String[] args) {
Demo demo = new Demo();
System.out.printf("%x\n",demo.hashCode());
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
输出:
15db9742
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 42 97 db (00000001 01000010 10010111 11011011) (-610844159)
4 4 (object header) 15 00 00 00 (00010101 00000000 00000000 00000000) (21)
8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
根据对象头的结构,我们知道前25个bit没有用,之后31个bit存放hashCode,那可以看出我的机器是小端序的。另外,我们还可以看到Mark Word尾三位是001,代表无锁,非常合理。
现在写个程序来测测看锁状态是怎么变化的
public class TestObjectHeader {
public static void main(String[] args) {
Demo demo = new Demo();
//加锁前状态
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
synchronized (demo) {
//加锁时状态 System.out.println("=============================================================================================");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
//锁释放后状态
System.out.println("=============================================================================================");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
输出:
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=============================================================================================
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 f7 1f d7 (10010000 11110111 00011111 11010111) (-685770864)
4 4 (object header) 32 00 00 00 (00110010 00000000 00000000 00000000) (50)
8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=============================================================================================
AboutSynchronized.Demo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
可以看到Mark Word尾三位从 001 变位 000 再变为 001,也就是从无锁,变为轻量级锁,再是无锁。