Java-synchronized实现详解(从Java到汇编)

这篇具有很好参考价值的文章主要介绍了Java-synchronized实现详解(从Java到汇编)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

synchronized作为java语言中的并发关键词,其在代码中出现的频率相当高频,大多数开发者在涉及到并发场景时,一般都会下意识得选取synchronized。

synchronized在代码中主要有三类用法,根据其用法不同,所获取的锁对象也不同,如下所示:

  • 修饰代码块:这种用法通常叫做同步代码块,获取的锁对象是在synchronized中显式指定的
  • 修饰实例方法:这种用法通常叫做同步方法,获取的锁对象是当前的类对象
  • 修饰静态方法:这种用法通常叫做静态同步方法,获取的锁对象是当前类的类对象

下面我们一起来测试下三种方式下,对象锁的归属及锁升级过程,SynchronizedTestClass类代码如下:

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedTestClass {
    private Object mLock = new Object();
    public void testSynchronizedBlock(){
        System.out.println("before get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
        synchronized (mLock) {
            System.out.println("testSynchronizedBlock start:"+Thread.currentThread().getName());
            System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("testSynchronizedBlock end:"+Thread.currentThread().getName());
        }
    }

    public synchronized void testSynchronizedMethod() {
        System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(this).toPrintable());
        System.out.println("testSynchronizedMethod start:"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("testSynchronizedMethod end:"+Thread.currentThread().getName());
    }

    public static synchronized void testSynchronizedStaticMethod() {
        System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(SynchronizedTestClass.class).toPrintable());
        System.out.println("testSynchronizedStaticMethod start:"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("testSynchronizedStaticMethod end:"+Thread.currentThread().getName());
    }
}

同步代码块

在main函数编写如下代码,调用SynchronizedTestClass类中包含同步代码块的测试方法,如下所示:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

运行结果如下:

Java-synchronized实现详解(从Java到汇编)

从上图可以看出在线程2获取锁前,mLock处于无锁状态,等线程2获取锁后,mLock对象升级为轻量级锁,等线程1获取锁后升级为重量级锁,有同学要问了,你在多线程与锁中不是说了synchronized锁升级有四个吗?你是不是写BUG了,当然没有啊,现在我们来看看偏向锁去哪儿了?

偏向锁

对于不同版本的JDK而言,其针对偏向锁的开关和配置均有所不同,我们可以通过执行java -XX:+PrintFlagsFinal -version | grep BiasedLocking来获取偏向锁相关配置,执行命令输出如下:

Java-synchronized实现详解(从Java到汇编)

从上图可以看出在JDK 1.8上,偏向锁默认开启,具有4秒延时,那么我们修改main内容,延时5秒开始执行,看看现象如何,代码如下:

public static void main(String[] args) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });

    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

输出如下:

Java-synchronized实现详解(从Java到汇编)

从上图可以看出在延迟5s执行后,mLock锁变成了无锁可偏向状态,结合上面两个示例,我们可以看出,在轻量级锁和偏向锁阶段均有可能直接升级成重量级锁,是否升级依赖于当时的锁竞争关系,据此我们可以得到synchronized锁升级的常见过程,如下图所示:

Java-synchronized实现详解(从Java到汇编)

可以看出,我们遇到的两种情况分别对应升级路线1和升级路线4。

同步方法

使用线程池调用SynchronizedTestClass类中的同步方法,代码如下:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });

    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });
}

运行结果如下:

Java-synchronized实现详解(从Java到汇编)

可以看出,在调用同步方法时,直接升级为重量级锁,同一时刻,有且仅有一个线程在同步方法中执行,其他函数在同步方法入口处阻塞等待。

静态同步方法

使用线程池调用SynchronizedTestClass类中的静态同步方法,代码如下

    public static void main(String[] args) {
        ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
        testSynchronizedBlock.execute(new Runnable() {
            @Override
            public void run() {
                SynchronizedTestClass.testSynchronizedStaticMethod();
            }
        });
        testSynchronizedBlock.execute(new Runnable() {
            @Override
            public void run() {
                SynchronizedTestClass.testSynchronizedStaticMethod();
            }
        });
    }

运行结果如下:

Java-synchronized实现详解(从Java到汇编)

可以看出,在调用静态同步方法时,直接升级为重量级锁,同一时刻,有且仅有一个线程在静态同步方法中执行,其他函数在同步方法入口处阻塞等待。

前面我们看的是多个线程竞争同一个锁对象,那么假设我们有三个线程分别执行这三个函数,又会怎样呢?代码如下:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            SynchronizedTestClass.testSynchronizedStaticMethod();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

运行结果:

Java-synchronized实现详解(从Java到汇编)

可以看到,3个线程各自运行,互不影响,这也进一步印证了前文所说的锁对象以及MarkWord中标记锁状态的概念。

synchronized实现原理

上面已经学习了synchronized的常见用法,关联的锁对象以及锁升级的过程,接下来我们来看下synchronized实现原理,仍然以上面的SynchronizedTestClass为例,查看其生成的字节码来了解synchronized关键字的实现。

同步代码块

testSynchronizedBlock其所对应的字节码如下图所示:

Java-synchronized实现详解(从Java到汇编)

从上图代码和字节码对应关系可以看出,在同步代码块中获取锁时使用monitorenter指令,释放锁时使用monitorexit指令,且会有两个monitorexit,确保在当前线程异常时,锁正常释放,避免其他线程等待死锁。

所以synchronized的同步机制是依赖monitorenter和monitorexit指令实现的,而这两个指令操作的就是mLock对象的monitor锁,monitorenter尝试获取mLock的monitor锁,如果获取成功,则monitor中的计数器+1,同时记录相关线程信息,如果获取失败,则当前线程阻塞。

Monitor锁就是存储在MarkWord中的指向重量级锁的指针所指向的对象,每个对象在构造时都会创建一个Monitor锁,用于监视当前对象的锁状态以及持锁线程信息,

同步方法

testSynchronizedMethod其所对应的字节码如下图所示:

Java-synchronized实现详解(从Java到汇编)

可以看到同步方法依赖在函数声明时添加ACC_SYNCHRONIZED标记实现,在函数被ACC_SYNCHRONIZED修饰时,调用该函数会申请对象的Monitor锁,申请成功则进入函数,申请失败则阻塞当前线程。

静态同步方法

testSynchronizedStaticMethod其所对应的字节码如下图所示:

Java-synchronized实现详解(从Java到汇编)

和同步方法相同,同步静态方法也是在函数声明部分添加了ACC_SYNCHRONIZED标记,与同步方法不同的是,此时申请的是该类的类对象的Monitor锁。


扩展

上文中针对synchronized的java使用以及字节码做了说明,我们可以看出synchronized是依赖显式的monitorenter,monitorexit指令和ACC_SYNCHRONIZED实现,但是字节码并不是最靠近机器的一层,相对字节码,汇编又是怎么处理synchronized相关的字节码指令的呢?

我们可以通过获取java代码的汇编代码来查看,查看Java类的汇编代码需要依赖hsdis工具,该工具可以从https://chriswhocodes.com/hsdis/下载(科学上网),下载完成后,在Intellij Idea中配置Main类的编译参数如下图所示:

Java-synchronized实现详解(从Java到汇编)

其中vm options详细参数如下:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedBlock -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedMethod -XX:+LogCompilation -XX:LogFile=/Volumes/Storage/hotspot.log

其中“compileOnly,”后面跟的是你要抓取的函数名称,格式为:*类名.函数名,LogFile=后指向的是存储汇编代码的文件。

环境变量配置如下:

LIBRARY_PATH=/Volumes/Storage/hsdis

这里的写法是:hsdis存储路径+/hsdis

随后再次运行Main.main即可看到相关汇编代码输出在运行窗口,通过分析运行窗口输出的内容,我们可以看到如下截图:

Java-synchronized实现详解(从Java到汇编)

可以看出在运行时调用SynchronizedTestClass::testSynchronizedMethod时,进入synchronized需要执行lock cmpxchg以确保多线程安全,故synchronized的汇编实现为lock cmpxchg指令。

参考链接

https://juejin.cn/post/6844904038580879367

https://segmentfault.com/a/1190000014315651?u_atoken=1de4c0b1-3bbd-4f24-a1ab-a997765e1e1a&u_asession=01_GMzG_R8y1NzmMkw-_FjCBDyAkKZZeqX02Aj0fLUi3NY0u-oBsC7Dy3mYDlaYjVfX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_YrukAhz0rn57pEYiZgVtjUe3R9QHfzEvknA4dzJmVTGBkFo3NEHBv0PZUm6pbxQU&u_asig=05zOyEpzkQtID72ACuBpeb8Glaue6lNWy0NQylEbR2Xc_7kNzOQ26VSExsqtnzBe0Xx0y_nHbPn6RxgW3P4ycjRX6ouZzhKXbyHd2wyK1BU-yIj5LEU571xoQb6N65-U8YiNzYYvkK1yxbcelpg93XN_0VgtmNHNEhLa9ouEeFbkf9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzbnmdT2ThIIvmF2G_c1IyFPFdBjB2ky9pbFgAVRR13T0H_8T8uYGNepqxdb-gLe1IO3h9VXwMyh6PgyDIVSG1W8yga-85GxBTGJYmsELhoUH8mRFOQ3P7irc0oGyG8T1Ftbng4zz7ikYTVlpim2ptLJ8hIoW81rm0R3x30M6VpJOmWspDxyAEEo4kbsryBKb9Q&u_aref=Vatq7Ew4O%2BKsO9AD17eeU57gwco%3D文章来源地址https://www.toymoban.com/news/detail-428717.html

到了这里,关于Java-synchronized实现详解(从Java到汇编)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java 并发编程】一文详解 Java 内置锁 synchronized

    存在共享数据; 多线程共同操作共享数。 synchronized 可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时 synchronized 可以保证一个线程的变化可见(可见性),即可以代替 volatile。 多线程编程中,有可能会出现多个线程同时访问同一个共享、可变

    2024年02月02日
    浏览(29)
  • Java关键字之synchronized详解【Java多线程必备】

    点击   Mr.绵羊的知识星球  解锁更多优质文章。 目录 一、介绍 二、特性 1. 线程安全 2. 互斥访问 3. 可重入性 4. 内置锁 三、实现原理 四、和其他锁比较 1. 优点 2. 缺点 五、注意事项和最佳实践 六、使用案例 1. 案例一 2. 案例二     synchronized是Java中最基本的同步机制之一,

    2024年01月24日
    浏览(38)
  • 【并发编程】深入理解Java并发之synchronized实现原理

    分析: 通过 new MyThread() 创建了一个对象 myThread ,这时候堆中就存在了共享资源 myThread ,然后对 myThread 对象创建两个线程,那么thread1线程和thread2线程就会共享 myThread 。 thread1.start() 和 thead2.start() 开启了两个线程,CPU会随机调度这两个线程。假如 thread1 先获得 synchronized 锁,

    2024年02月04日
    浏览(50)
  • java八股文面试[多线程]——Synchronized的底层实现原理

    笔试:画出Synchronized 线程状态流转 实现原理图 synchronized解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是 同步 ,也称之为”同步锁“。 synchronized的作用是保证在 同一时刻 , 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的

    2024年02月10日
    浏览(37)
  • synchronized 详解

    synchronized是一个对象锁,这个是前提 要使得synchronized 生效,一定是两者用的是同一把锁,这个时候,才会有限制作用。 注意区分:锁定的是类对象,还是类的实例。 synchronized加在静态方法上锁定的是类对象,类对象在JVM中只有一个。 而加在普通方法上,锁定的是this指代的

    2024年02月01日
    浏览(22)
  • JavaWeb——synchronized详解

    目录 一、特性 1、互斥性 2、不可中断性 3、可重入性 二、使用 1、修饰普通方法 2、修饰静态方法 3、修饰代码块 三、锁机制 当线程进入synchronized修饰的代码块时,就相当于加锁。 当线程退出synchronized修饰的代码块时,就相当于解锁。 在同一时刻只允许一个线程持有某个对

    2023年04月16日
    浏览(22)
  • synchronize锁详解

    目录 1、什么是synchronize? 2、为什么要用synchronize锁? 2.1 代码演示 2.2 原因分析 2.3 专有名词解释 2.3.1 临界资源 2.3.2 临界区 2.3.3 竞态条件 3、synchronize锁的原理 3.1 锁升级过程 3.1.1 偏向锁 3.1.2 轻量级锁 3.1.3 重量级锁 3.1.4 总体过程 3.2 锁优化 3.2.1 自旋锁 3.2.2 锁粗化 3.2.3 锁消

    2024年02月02日
    浏览(25)
  • 并发-synchronized详解

    JDK1.6之前的synchronized一来就直接给对象加了一把重量级锁,频繁地在用户态和内核态之间切换,导致性能非常低。为了弥补synchronized的不足,大佬doug lee写了一个AQS框架,用Java语言实现了ReentrantLock。然后在JDK1.6之后,oracle优化了synchronized的锁过程,增加了锁的膨胀逻辑

    2024年02月12日
    浏览(25)
  • Java实现创建链表与打印链表元素(可作为模板)

    1、通过数组元素值,构造一个单向链表; 2、将链表元素以数组的形式打印出来,如“[1, 2, 3, 4]”

    2024年02月05日
    浏览(29)
  • Java——》synchronized的使用

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月09日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包