private static int i = 1;
public synchronized static void increase1(){
i++;
}
public synchronized void increase2(){
i++;
}
public void increase3(){
synchronized (LOCK){
i++;
}
}
}
`#increase1`方法表示的是修饰静态方法;`#increase2`方法表示的是修饰实例方法;`#increase1`方法表示的是修饰代码块。
[](
)三、锁存储布局
--------------------------------------------------------------------------
`synchronized`始终与对象关联。如果方法是静态的,那么关联的对象就是类;如果该方法是非静态的,则关联的对象是实例。如果是代码块,那么就是指定的对象。很显然,锁是记录于对象中。那么问题来了,`synchronized`的锁具体指的是什么呢?简单理解锁就是一个共享的资源,记录了谁占有它,当前状态是什么等等。我们先来分析对象在内存中是如何存储的。
在`Hotspot JVM` 中,`Java Object`对象在内存中存储中的存储布局分为三个区域,分别是对象头、示例数据、对象填充。如图所示,数组和对象的存储布局十分相似,只是对象的头部大于数组的长度,因为数组需要存储自身的长度,为4Byte。
![](https://s2.51cto.com/images/20210913/1631472150218393.jpg)
从图上可以看出,对象头部包括两部分,分别是对象标记和类元信息(类型指针)。对象标记,也就是`Markword`存储对象的 `hashCode`、 `GC` 信息和锁等信息。类元信息存储“类对象信息的指针”。在32位的 `JVM` 中,对象头占用8个byte,另外在64位的 `JVM` 占用16个字节。
![](https://s2.51cto.com/images/20210913/1631472151392170.jpg)
如上图所示,这个是`Markword`类在32位的 `JVM` 的各种情况存储布局,`Markword` 里面存储的数据会随着锁标志位的变为而变化,大致存储的变化共分为五种情况。我们可以从图上看到从无锁->偏向锁->轻量级锁->重量级锁存储的变化过程,这个就是锁升级的过程。
那么问题来了,是不是所有的对象都能实现锁呢?答案是肯定的。
* 首先我们对于`Java`有一个共有的认知,那就是所以的对象都派生自`Object`,每个Object在内存中存储都如我们图上所示的,都有对象头,对象头中有`Markword`对象标记。 需要注意的是,对象存储包括`Markword`对象标记的实现都是`native`的,都是`C++`语言实现的对象。
* 线程在获取锁时,实际获取的是一个监视器(monitor)对象,这是一个同步对象,所有的`Java Object`都包含这个对象。同样的,这个对象也是`native`的。
[](
)四、锁升级
------------------------------------------------------------------------
`Java 1.6`之前,`synchronized`是标准的重量级锁,多个线程竞争共享资源时,未竞争到资源的线程会一直处于阻塞状态,性能开销很大,同时对于重量级锁,对于加锁和释放锁也有很多的资源消耗。为了减少性能开销,提升效率,人们针对不同的加锁场景,细分了四种锁状态,包括无锁、偏向锁、轻量级锁,重量级锁,锁的状态会根据线程竞争资源的激烈程度从低到高不断升级。
### [](
)4.1 偏向锁
很多时候,锁总是被同一线程多次获取,并没有线程竞争锁。对于这样的情况,偏向锁就很适用,那到底什么时候偏向锁呢?在第三章节,我们列出了在`synchronized`不同的锁状态下,`Markword`内存布局有很大的差异。
#### [](
)4.1.1 偏向锁获取
![](https://s2.51cto.com/images/20210913/1631472151741615.jpg)
当一个线程去访问`synchronized`关键字修饰的代码块或方法时,会在`Markword`中存储当前线程的ID,当再有线程想尝试进入同步块时,会先通过`CAS`比较当前`Markword`存储的线程ID是否为尝试进入同步块的线程ID,如果相等,不需要再次获取锁了,可直接执行同步代码块;如果不相等,说明当前偏向锁是偏向于其它线程,需要撤销偏向锁,然后将锁升级成轻量级锁。
#### [](
)4.1.2 偏向锁撤销
撤销偏向锁并不是将锁真正的撤销,成为无锁的状态。对于偏向锁的撤销,对原持有的线程和锁本身有两种情况。
* 如果原持有线程刚好执行完了,退出同步代码块,那么这个时候会把`Markword`保存的线程ID设置为空。
* 如果原持有线程仍在同步代码块中执行,这个时候偏向锁会升级为轻量级锁,然后原有线程继续执行。
下面图演示在`synchronized`修饰的同步代码块下,线程T1和线程T2先后竞争锁资源的流程。
![](https://s2.51cto.com/images/20210913/1631472152821560.jpg)
**Java面试核心知识点笔记**
其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。
![蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6](https://s2.51cto.com/images/20210913/1631472152871282.jpg)
**Java中高级面试高频考点整理**
![蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6](https://s2.51cto.com/images/20210913/1631472152159508.jpg)
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](https://ali1024.coding.net/public/P7/Java/git)**
![蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6](https://s2.51cto.com/images/20210913/1631472152344992.jpg)
**最后分享Java进阶学习及面试必备的视频教学**
![蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6](https://s2.51cto.com/images/20210913/1631472153709432.jpg)
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/150565.html