Synchronized一般都是用在下面这几种场景:
- 修饰实例方法,对当前实例对象this加锁
- 修饰静态方法,对当前类的Class对象加锁
- 修饰代码块,指定一个加锁的对象,给对象加锁
其实就是锁方法、锁代码块和锁对象,那他们是怎么实现加锁的呢?
在这之前,我就先跟大家聊一下我们Java对象的构成
在 JVM 中,对象在内存中分为三块区域:
- 对象头
- Mark Word(标记字段):默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
- Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 实例数据
- 这部分主要是存放类的数据信息,父类的信息。
- 对其填充
- 由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
Tip:不知道大家有没有被问过一个空对象占多少个字节?就是8个字节,是因为对齐填充的关系哈,不到8个字节对其填充会帮我们自动补齐。

我们经常说到的,有序性、可见性、原子性,synchronized又是怎么做到的呢?
有序性
CPU会为了优化我们的代码,会对我们程序进行重排序。
as-if-serial
不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的,还有就是有数据依赖的也是不能重排序的。
就比如:
int a = 1;
int b = a;
这两段是怎么都不能重排序的,b的值依赖a的值,a如果不先赋值,那就为空了。
可见性
需要说到JMM(Java内存模型),JMM并不是实际存在的,而是一套规范,这个规范描述了很多java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节,Java内存模型是对共享数据的可见性、有序性、和原子性的规则和保障。

原子性
其实他保证原子性很简单,确保同一时间只有一个线程能拿到锁,能够进入代码块这就够了。
这几个是我们使用锁经常用到的特性,那synchronized他自己本身又具有哪些特性呢?
可重入性
synchronized锁对象的时候有个计数器,他会记录下线程获取锁的次数,在执行完对应的代码块之后,计数器就会-1,直到计数器清零,就释放锁了。
那可重入有什么好处呢?
可以避免一些死锁的情况,也可以让我们更好封装我们的代码。
不可中断性
不可中断就是指,一个线程获取锁之后,另外一个线程处于阻塞或者等待状态,前一个不释放,后一个也一直会阻塞或者等待,不可以被中断。
值得一提的是,Lock的tryLock方法是可以被中断的。