文章已收录Github精选,欢迎Star:https://github.com/yehongzhi
前言
作为Java程序员,我们都知道在多线程的情况下,为了保证线程安全,经常会使用synchronized和Lock锁。Lock锁之前写过一篇《不得不学的AQS》,已经详细讲解过Lock锁的底层原理。这次我们讲一下日常开发中常用的关键字synchronized,想要用得好,底层原理必须要搞明白。
synchronized是JDK自带的一个关键字,在JDK1.5之前是一个重量级锁,所以从性能上考虑大部分人会选择Lock锁,不过毕竟是JDK自带的关键字,所以在JDK1.6后对它进行优化,引入了偏向锁,轻量级锁,自旋锁等概念。
一、synchronized的使用方式
在语法上,要使用synchronized关键字,需要把任意一个非null对象作为”锁”对象,也就是需要一个对象监视器(Object Monitor)。总的来说有三种用法:
1.1 作用在实例方法
修饰实例方法,相当于对当前实例对象this加锁,this作为对象监视器。
1 | public synchronized void hello(){ |
1.2 作用在静态方法
修饰静态方法,相当于对当前类的Class对象加锁,当前类的Class对象作为对象监视器。
1 | public synchronized static void helloStatic(){ |
1.3 修饰代码块
指定加锁对象,对给定对象加锁,括号括起来的对象就是对象监视器。
1 | public void test(){ |
二、synchronized锁的原理
在讲原理前,我们先讲一下Java对象的构成。在JVM中,对象在内存中分为三块区域:对象头,实例数据和对齐填充。如图所示:

对象头:
- Mark Word,用于存储对象自身运行时的数据,如哈希码(Hash Code),GC分代年龄,锁状态标志,偏向线程ID、偏向时间戳等信息,它会根据对象的状态复用自己的存储空间。它是实现轻量级锁和偏向锁的关键。
- 类型指针,对象会指向它的类的元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。
- Array length,如果对象是一个数组,还必须记录数组长度的数据。
实例数据:
- 存放类的属性数据信息,包括父类的属性信息。
对齐填充:
- 由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
2.1 同步代码块原理
为了看底层实现原理,使用javap -v xxx.class命令进行反编