synchronized锁的实现原理,从多方面分析

01 监视器锁(monitor)/重量级锁

  monitor对象存在于每个Java对象的对象头中(存储指针的指向),synchronized锁便是通过这种方式获取锁的。
 监视器锁本质是依赖于底层操作系统的Mutex Lock(互斥锁)来实现。操作系统实现线程间的切换需要从用户态转换到内核态,成本非常高,状态间的转换需要相对比较长的时间,有可能切换时间比代码执行时间还要长,导致Synchronized效率低,这种依赖于操作系统Mutex Lock实现的锁称为重量级锁

 每个对象都有一个monitor与之关联,对象与其monitor间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机中,monitor由C++的ObjectMonitor实现。ObjectMonitor中有两个队列,分别为 _WaitSet和 _EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象)。

ObjectWaiter重要属性
_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放处于wait状态的线程队列
_EntryList 存放处于等待锁阻塞状态的线程队列
_recursions 锁重入次数
_count 记录所对象是否被锁定,count=0表示锁没被占用,同步代码进去+1,出去减1

当线程获取对象锁时,先判断_count是否等于0,等于0说明锁没被占用,就可获取锁,如果大于0,说明被其他线程占用,就进入_EntryList阻塞,当多个线程同时访问一段同步代码时,先会进入 _EntryList集合,当线程获取到对象的monitor后进入 _owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器count加1,若线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,count减1,同时该线程进入 _WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor并复位变量的值,以便其他线程进入获取monitor。

synchronized底层实现,加在方法上和加在同步代码块中编译后的区别、类锁、对象锁。


对象锁

  (1) 这个对象内部得有一个标志位(state变量),记录有没有被某个线程占用
       最简单state有0和1两个取值,0表示没有被占用,1表示有某个线程占用了这个锁
  (2) 如果这个对象被某个线程占用,记录这个线程的thread ID
  (3) 这个对象维护一个thread id list,记录其他所有阻塞的、等待获取拿这个锁的线程。在当前线程释放锁之后从这个thread id list里面取一个线程唤醒

  在对象头里,有一块数据叫Mark Word。在64位机器上,Mark Word是8字节(64位)的,这64位中有2个重要字段:锁标志位和占用该锁的thread ID。因为不同版本的JVM实现,对象头的数据结构会有各种差异

对象头是实现synchronized对象锁的基础,synchronized使用的锁对象是存储在Java对象头里,它是轻量级锁和偏向锁的关键


从字节码层面解释,同步代码快

有如下代码

public class Test {
    public static void main(String[] args) {

    }
    Object object = new Object();
    public void test() {
        synchronized (object) {
            System.out.println(object);
        }
    }
}

先编译,再反编译

E:\java\ideaProjects\jdbcTest\target\classes\com\test>javap -c Test.class
Compiled from "Test.java"
public class com.test.Test {
java.lang.Object object;
public com.test.Test();
Code:
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: new           #2                  // class java/lang/Object
8: dup
9: invokespecial #1                  // Method java/lang/Object."<init>":()V
12: putfield      #3                  // Field object:Ljava/lang/Object;
15: return
public static void main(java.lang.String[]);
Code:
0: return
public void test();
Code:
0: aload_0
1: getfield      #3                  // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_0
11: getfield      #3                  // Field object:Ljava/lang/Object;
14: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
17: aload_1
18: monitorexit
19: goto          27
22: astore_2
23: aload_1
24: monitorexit
25: aload_2
26: athrow
27: return
Exception table:
from    to  target type
7    19    22   any
22    25    22   any
}

从字节码中可以看到有一个监视器进入指令monitorenter和两个监视器退出指令monitorexit
有两个退出一个是正常退出,一个是异常退出,如果代码有异常,则就只会有一个monitorexit


从字节码层面解释–同步方法
如下代码进行反编译

public class Test {
    public static void main(String[] args) {
    }

    public synchronized void test() {
        System.out.println("123");
    }
}

可以看到如下结构,其中ACC_SYNCHRONIZED就是同步方法的锁
调用指令会先检查方法的ACC_SYNCHRONIZED访问标志知否被设置,如果设置了,必须先持有monitor锁才能先访问

public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #3                  // String 123
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
LocalVariableTable:
Start  Length  Slot  Name   Signature
0       9     0  this   Lcom/test/Test;
}

从字节码层面解释—同步静态代码块
代码

public class Test {
    public static void main(String[] args) {
    }

    public static synchronized void test() {
        System.out.println("123");
    }
}

反编译之后,可以看到,相对于同步方法,静态同步方法多了ACC_STATIC

public static synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #3                  // String 123
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值