synchronized关键字底层原理解析

一、前言

        大家想必都知道,当我们再并发场景下要想使用或者操作一个全局的共享变量时,可能会出现数据不安全的情况,这也就是我们通常所说的线程安全问题,为了保证数据的安全性可靠性,我们就需要对线程进行同步,这也就是synchronized关键字出现的背景了。

二、用法及实现方式

        synchronized可以修饰普通方法、静态方法,同时还可以直接定义代码块,但是归根结底它上锁的资源只有两类:一个是对象,一个是

        修饰普通方法时,synchronized的锁对象是当前类的实例化对象(this);

        修饰静态方法时,锁对象是为当前类class对象,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;

        作用在同步代码块上时,理论上锁对象可以为任意非空对象;

        除了锁对象的不同之外,synchronized加在方法上和加在代码块上的是实现方式也不一样:

        经过反编译,查看其字节码文件我们可以得出,加在方法上时,会通过一个ACC_SYNCHRONIZED的标志来告诉JVM这是一个同步方法,在进入该方法之前先获取相应的锁,锁的计数器加1,方法结束后计数器-1,如果获取失败就阻塞住,知道该锁被释放;而在代码块上,是由monitorenter指令进入,然后monitorexit释放锁,在执行monitorenter之前需要尝试获取锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,那么就把锁的计数器加1。当执行monitorexit指令时,锁的计数器也会减1。当获取锁失败时会被阻塞,一直等待锁被释放。

三、原理

        synchronized实现线程同步的基础是对锁对象的获取。Java是面向对象编程,因此可以说任何非空对象都可以作为锁。那synchronized底层是如何运用锁对象来实现线程同步的呢,这里涉及到对象的组成。

        堆内存中的对象包括对象头实例数据填充数据这几个部分。对象锁的一些信息就是存放在对象头中的。有锁标志位、是否是偏向锁,当然还有对象的哈希值及GC分代年龄等信息。

synchronized关键字底层原理解析_第1张图片

(图片出自:https://zhuanlan.zhihu.com/p/75880892)

        当一个线程A去获取锁的时候,会判断锁标志位是否处于无锁状态(01),若是,则成功获取到锁,此时锁的状态为偏向锁(锁标志位仍为01),对象头中会记录当前线程id,实现了同一线程的可重入。在A线程还没有释放锁的情况下,线程B这时也来获取该锁,发现锁状态为偏向锁,获取不到锁,会不断重试去获取锁,并会将锁状态修改为00,也就是 轻量级锁;当然线程B也不会永远重试,当达到一定次数(10次)后,仍没有获取到锁,则将锁升级为重量级锁(10),线程B也会进入阻塞状态,等待被唤醒后重新获取锁。不论是哪种锁状态,始终会保持同步方法或者同步代码块中同一时间只有一个线程在执行,从而保证线程安全。

       这就是synchronized关键字在实现线程同步时获取锁的的锁升级的一个过程。

        

你可能感兴趣的:(java,多线程,并发编程)