Synchronized底层实现

1)先在Idea下载一个ByteCode插件来观察java经过编译之后的字节码

public class TestSync {
    synchronized void m() {

    }

    void n() {
        synchronized (this) {//monitorenter 

        } //monitorexit
    }

    public static void main(String[] args) {

    }
}

然后idea—view—showByteCode

这是我们n方法的字节码 为synchronized关键字会在同步块前后增加monitorenter monitorexit指令

Synchronized和Lock的实现原理和锁升级

在虚拟机规范对monitorenter和monitorexit的行为描述中,有两点需要注意。

首先synchronized同步块对同一线程来说是可重入的,不会出现自己把自己锁死的问题

其次,同步块在已进入的线程执行完成之前,会阻塞后面其他线程的进入

还有:方法及的同步是隐式的,即无须通过字节码指令来控制,它实现在方法的调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。

2)JVM层面

所谓给对象上锁,就是对象头上产生了变化,锁信息就是存在MarkWord上面,加锁就是修改MarkWord

用JOL工具观察内存布局:(JOL就是maven里面的一个jar包 可以用来观察java内存的布局和大小)

Synchronized和Lock的实现原理和锁升级

观察Synchronized锁升级的过程,只需要观察对象MarkWord的变化就行(最后两个字节是锁标志位)

Synchronized和Lock的实现原理和锁升级

C C++调用了操作系统提供的同步机制

3)OS和硬件层面

X86 : lock cmpxchg / xxx 实现CAS操作的最终指令  (lock后面的指令执行的过程中 区域被lock锁定,只有我这个指令能执行)

https://blog.csdn.net/21aspnet/article/details/88571740

总结:synchronized是基于jvm底层实现的数据同步 加锁解锁过程由JVM自动控制,

Synchronized锁升级的过程

先说下什么是重量级锁:JDK早其sysnchronized叫是重量级锁,申请资源必须通过kernel,需要从用户空间切换到内核空间(从用户态向内核态调用)拿到锁,然后把状态返回给用户空间—惊动操作系统老大

    用户空间做一些比较关键的事情 需要通过老大(OS)来做,读写网络,写硬盘,比较敏感的操作必须通过操作系统进行,可以保证操作系统比较健壮!

偏向锁和自旋锁都不需要惊动操作系统老大。

重量级锁:JDK早其sysnchronized叫是重量级锁,申请资源必须通过kernel,需要从用户空间切换到内核空间(从用户态向内核态调用)—惊动操作系统老大

     当竞争的线程特别多时,自旋锁就不适用了(一个线程运行,剩下的都在自旋) 

     重量锁:其他线程都进队列等着(等待队列),不需要在那里转,占用CPU资源了

自旋锁(轻量级锁):当出现其他线程竞争的时候,发现markword里面已经有其他线程id了

          首先撤销偏向锁的状态, 然后 以CAS的方式修改MarkWord 谁修改成功了就算谁的(MarkWord记着指向线程中的lockRecord指针)

          类似数据库的乐观锁(乐观锁有版本号,而自旋锁是比较操作的值,存在ABA问题)

         指向线程栈中的LockRecord,记录了线程被锁住多少次(Syn是可重入锁)

偏向锁:偏向锁是有偏向的,偏向于某个线程,不需要惊动操作系统,把字节的线程Id记到MarkWord里面

         在JDK类库中,大多数只在一个线程里面运行(比如StringBuffer)为了一个线程还要惊动操作系统;比较浪费

    偏向锁连CAS都不做了(消除数据在无竞争情况下的同步原语)

    凡是有人第一次得到这把锁的时候(把线程的Id放到MarkWord里面),

自旋锁什么时候升级成重量级锁

JDK1.6之前:在某一个线程自旋次数超过十次就会升级成重量级锁

JDK1.6之后:自适应自旋,JDK根据线程运行情况自己判断

锁升级的过程

Synchronized和Lock的实现原理和锁升级

普通对象和匿名偏向的区别:因为JVM启动4s之后才会启动偏向锁,(利用4s钟的时间判断 需不需要启动偏向锁,如果JVM能确定会有多个线程争抢某些对象 则不需要启动偏向锁)

  所以在程序前4s new出来的对象是普通对象

  4s之后new出现的对象是匿名偏向(没有偏向任何人)

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        String s = ClassLayout.parseInstance(o).toPrintable();
        System.out.println(s);
        TimeUnit.SECONDS.sleep(5);
        Object o2 = new Object();
        String s2 = ClassLayout.parseInstance(o2).toPrintable();
        System.out.println(s2);
    }

这是测试程序,观察最后两位字节

Synchronized和Lock的实现原理和锁升级
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
View Code

相关文章:

  • 2022-12-23
  • 2021-06-02
  • 2021-12-22
  • 2021-06-06
  • 2022-02-14
  • 2021-11-09
  • 2021-12-20
  • 2021-07-22
猜你喜欢
  • 2021-06-27
  • 2022-12-23
  • 2021-05-13
  • 2022-02-22
  • 2022-02-17
  • 2022-02-27
  • 2022-12-23
相关资源
相似解决方案