【Java学习笔记】多线程 Part 2 - synchronized

1. synchronized 锁住了什么?

sychronized可以锁住的是对象实例或者

  1. 在静态方法加锁,锁住类
public static synchronized void myfunction(){}
  1. 在普通方法加锁,锁住对象实例
public synchronized void myfunction(){}
  1. 方法中使用同步代码块
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_locklock
无锁001
偏向锁101
轻量级锁-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,也就是从无锁,变为轻量级锁,再是无锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值