在java多线程并发编程中,
一直占有很重要的角色。
- Synchronized
通过获取锁来实现同步。先来看一下,它的使用方法:
- Synchronized
- package com.Vinctor.Tst;
- public class VinctorSyncDemo {
- public static synchronized void staticSyncMethod() {
- System.out.println("static synchronized");
- }
- public synchronized void normalSyncMethod() {
- System.out.println("normal SyncMethod");
- }
- public void normalInnerMethod() {
- synchronized(this) {
- }
- }
- public void staticInnerMethod() {
- synchronized(VinctorSyncDemo.class) {
- }
- }
- }
如上,分为三种方式:
,修饰静态方法,锁是当前类的
- staticSyncMethod
,位于方法区
- 类对象
,修饰普通实例方法,锁是当前
- normalSyncMethod
,位于堆
- 实例对象
与
- normalInnerMethod
,其中
- staticInnerMethod
与
- synchronized (this)
效果相同,
- normalSyncMethod
与
- synchronized (VinctorSyncDemo.class)
效果相同。
- staticSyncMethod
我们将上述代码
之后,看一下字节码指令:
- javap
可以看到看到
方法在执行到synchronized代码块时,有两个指令
- staticInnerMetho
和
- monitorenter
,而下面异常表指向的位置10,也同样执行了
- monitorexit
。可见同步代码块的实现是使用
- monitorexit
和
- monitorenter
两个代码块进行获取锁已释放锁的,发生异常之后,也同样会释放锁。JVM 必须保证
- monitorexit
有
- monitorenter
相对应。当监视器一个线程被持有时,那么这个线程就持有了锁,其他线程就不能获取这个监视器,直至
- monitorexit
释放锁。同步方法同样也可以用着两个指令获取和释放锁。
- monitorexit
当一个线程试图访问同步方法或者同步代码块时,首先需要获取锁,退出同步方法或者同步代码块时需要释放锁。可以看出锁在
中起到至关重要的作用。锁是什么呢?他是怎么存储的呢?
- Synchronized
的区域,存储着对象的 hashCode,分代年龄和锁标记位。如图:
- Mark Word
运行期间,mark word 中存储的数据锁的标记位的变化而变化,其可能变化情况如下:
可以看到,分为很多锁:偏向锁,轻量级锁,重量级锁。
内置锁按照状态分为:无锁状态,偏向锁,轻量级锁,重量级锁。四中状态随着锁的竞争加剧而升级,但是不是回退降级。(本文仅讨论 HotSpot)
虚拟机开发人员研究发现,大多数情况下,多线程存在竞争的情况很少,为了避免同一线程多次进行锁的获取,故引入了偏向锁的概念。
当一个线程A访问同步代码块的时候,
首先检查锁标志位:如果为
,再检查是否为偏向锁,
- 01(无锁或偏向锁)
,会通过 CAS 操作(下面将解释)在对象头中存储当前访问的线程 A 的ID ;
- 0(不是偏向锁)
,检查一下对象头中是否是当前线程A 的 ID,如果是,则表示获取到锁,执行代码块。接下文,
- 1(是偏向锁)
。
- 轻量级锁
锁升级轻量级锁之后,对象头中不在存储线程 ID 等信息,而是将这些信息拷贝至持有锁的线程栈中锁记录中,再将对象头指向该地址。上面的例子
来源: https://juejin.im/entry/5a0bef4a6fb9a045263b2005