synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

public static void main(String[] args) {
        //开启线程
        Thread t1 = new Thread(() -> {
          //todo
        });
        t1.start();
复制代码

如上一个简单的线程他从启动到调用run方法是怎么一个流程,往下看线程 start 方法的源码发现核心的方法应该就是调用 private native void start0(); 这个方法是 native 用c写的。这个方法底层在调用jvm,jvm直接调用操作系统 pthread_create 方法启动操作系统的一个线程,同时jvm维护一个线程对象跟操作系统的线程一一对应,同时和javaThread 一一对应,调用java的线程对象相关api相当于操作操作系统的线程,在操作系统的 pthread_create 的方法中执行的时候再来回调java的 run 方法。

java线程调用过程如下

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

环境准备

1.idae环境 2.需要依赖jol(可以打印出对象对象头)

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
复制代码

synchronized 是什么

synchronizedjava 的关键字,是 jvm 底层实现的,1.6前只有重量级,1.6 jvm 对其进行优化,增加了 偏向锁轻量级锁

前提

要了解 synchronized 锁膨胀过程需要了解对象的对象头 //todo 差一个对象头图片

偏向锁

顾名思义,偏向,偏心,偏向线程,当只有一个线程访问同步代码的时候,即是偏向锁,每次要执行同步代码的时候,都会先判断线程id是否一致,如果一致就直接进入同步块

偏向锁延迟

1.主线程不睡眠的情况

public static void main(String[] args) throws InterruptedException {
        User user = new User();
        //jvm延迟偏向锁,所以这时候无锁锁标志 01
        System.out.println("first print" + ClassLayout.parseInstance(user).toPrintable());
        //此时偏向锁关闭,进入同步代码块,变为轻量级锁 00
        synchronized (user) {
            System.out.println("first into sync" + ClassLayout.parseInstance(user).toPrintable());
        }
    }
复制代码

下面是根据jol打印出来的对象头 mark work

第一次打印: 00000001 00000000 00000000 00000000 锁标志位是01 无锁

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

第二次打印:
00001000 11110111 10010011 00000010

锁标志位00 轻量级锁

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

2.主线程睡眠的情况

public static void main(String[] args) throws InterruptedException {
//睡眠大概五秒之后,jvm偏向锁已经开启
        Thread.sleep(5000L);
        User user = new User();
        //偏向锁,还没有记录线程信息,表示可偏向 101
        System.out.println("first print" + ClassLayout.parseInstance(user).toPrintable());
        //进入同步代码块,先检查是否可偏向
        //如果可偏向,记录线程信息 101 同时记录线程id
        synchronized (user) {
            System.out.println("first into sync" + ClassLayout.parseInstance(user).toPrintable());
        }
    }
复制代码

下面是根据jol打印出来的对象头 mark work

第一次打印: 00000101 00000000 00000000 00000000 锁标志101 没有线程id,表示可偏向

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

第二次打印:
00000101 00101000 11110010 00000010

锁标志101 有线程id,表示已经偏向了

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

总结一下

同样的代码有睡眠跟没有睡眠锁的标志位不一样,睡眠五秒之后jvm已经开启了偏向锁,所以有睡眠的进入同步代码块就是偏向锁,没有睡眠的,jvm延迟了偏向锁,所以进入同步代码块是轻量级锁。

轻量级锁

偏向锁是一直由同一个线程持有锁,但是当发生多个 线程交替 执行同步块的时候就会进行锁升级。

以下是轻量级锁升级膨胀的过程

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

示例

public static void main(String[] args) throws InterruptedException {
  //睡眠五秒,五秒之后jvm开启轻量级锁
       Thread.sleep(5000L);
        User user = new User();
        //偏向锁,还没有记录线程信息,表示可偏向 101 无线程id
        System.out.println("first print" + ClassLayout.parseInstance(user).toPrintable());
        //进入同步代码块,先检查是否可偏向
        //如果可偏向,记录线程信息 101 有线程id
        synchronized (user) {
            System.out.println("first into sync" + ClassLayout.parseInstance(user).toPrintable());
        }
        //开启线程 此时不同的线程来了,不存在锁竞争,锁升级为轻量级锁
        Thread t1 = new Thread(() -> {
            synchronized (user) {
                System.out.println("sencond into sync" + ClassLayout.parseInstance(user).toPrintable());
            }
        });
        t1.start();
    }
    
复制代码

下面是根据jol打印出来的对象头 mark work

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

重量级锁

重量级锁是在多线程中,存在 锁竞争 的情况下会出现

示例

public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000L);
        User user = new User();
        //偏向锁,但是没有偏向的线程 101
        System.out.println("first pring"
                + ClassLayout.parseInstance(user).toPrintable());
        //主线程拿到锁
        synchronized (user) {
            //偏向锁,此时有偏向的地址 101
            System.out.println("first in"
                    + ClassLayout.parseInstance(user).toPrintable());
        }
        //线程t2拿到锁
        Thread t2 = new Thread(() -> {
            synchronized (user) {
                //虽然主线程已经执行完了,但是算是交替执行
                //此时是轻量级锁 00
                System.out.println("t2 "
                        + ClassLayout.parseInstance(user).toPrintable());
                try {
                    //进入锁,先不退出,等待t3来竞争锁
                    Thread.sleep(7000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();
        //等待是为了确保t2已经拿到锁了
        Thread.sleep(4000L);
        //线程t3竞争锁
        Thread t3 = new Thread(() -> {
            synchronized (user) {
                //t2还没执行完就去竞争锁
                //存在锁的竞争,锁升级为重量级锁 10
                System.out.println("t3 "
                        + ClassLayout.parseInstance(user).toPrintable());
            }
        });
        t3.start();
    }
    
复制代码

下面是根据jol打印出来的对象头 mark work

synchronized锁膨胀 偏向锁 轻量级锁 重量级锁
synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

原文 

https://juejin.im/post/5f114c06e51d45347246bcc8

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » synchronized锁膨胀 偏向锁 轻量级锁 重量级锁

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址