Synchronized实现原理及优化

一、synchronized实现原理

为了解决线程安全问题,java提供了synchronized关键字来保证线程的同步互斥,使得同一时间只有一个线程来访问共享资源,而这个同步互斥就来自于监视锁Monitor。

1.Java对象结构

说到监视锁Monitor我们不得不说一下Java对象的构成,如下图所示:
在这里插入图片描述
关于对象头更细一步的介绍可以参考:https://blog.csdn.net/scdn_cp/article/details/86491792.

在每一个对象的Mark Word中都有一个指向对象监视器Monitor的指针,synchronized关键词也是基于这个实现的。

2.synchronized实现原理

当一个线程访问同步代码块时,线程进入时需要获取到对象的监视器Monitor锁,在结束或者抛出异常时释放监视器Monitor锁。

monitor锁机制:在Java代码编译为字节码时,字节码文件开始的位置生成monitorenter指令,结束生成monitorexit指令。

  • monitorenter: 当线程进入同步代码块时,monitor被占用并会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权;简单理解就是:同步代码块进入时,执行monitorenter指令就获取到了对象的监视器monitor锁。
  • monitorexit: 执行monitorexit的线程必须是对象所对应的monitor的所有者,在synchronized代码块结束时,执行monitorexit指令来释放锁

我们来看一段代码:
第一种:synchronized代码块:

public class SynchronizedTest {
    public  void test() {
        synchronized (this) {
            System.out.println("hahha");
        }
    }

    public static void main(String[] args) {        
        SynchronizedTest synchronizedTest=new SynchronizedTest();
        synchronizedTest.test();
    }
}

通过" javap -c 文件名 "命令反编译成汇编代码:

在这里插入图片描述
我们可以看出在进入synchronized代码块时进入monitorenter指令获得锁,离开代码块时执行monitorexit指令并释放对象锁。

总结:

  • JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的。
  • 通过monitorenter和monitorexit指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。
  • JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。
  • 字节码中包含一个monitorenter指令以及多个monitorexit指令。这是因为Java虚拟机需要确保所获得的锁在正常执行路径,以及异常执行路径上都能够被解锁。

我们将代码改为sychronized加在普通实例方法上:
第二种:synchronized普通实例方法:

public class SynchronizedTest {
    public synchronized void test() {
            System.out.println("hahha");
    }

    public static void main(String[] args) {
        SynchronizedTest synchronizedTest=new SynchronizedTest();
        synchronizedTest.test();
    }
}

反编译结果图下:
在这里插入图片描述

synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。(见博客 https://www.cnblogs.com/javaminer/p/3889023.html.)

二、synchronized优化

在JDK1.5之前synchronized是一个重量级锁,效率比较低。随着JDK1.6对synchronized进行的各种优化后,synchronized并不会显得那么重了。
在jdk1.6后,synchronized具体被优化包括使用JDK5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁 这些优化策略。

1、自适应的CAS自旋锁

自适应自旋锁能够智能地根据它得到的信息判断是否要自旋。自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

优点:这样能够减少自旋的次数提高效率。

2、偏向锁

对象的代码如果一直被同一线程执行,不存在多个线程竞争,这种情况下,该线程在后续的执行中就直接自动获取锁,这样就降低获取锁带来的性能开销。因此就引进了偏向锁。

偏向锁: 指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。

偏向锁的撤销:如果在某个时间点上没有字节码正在执行,就先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;如果线程处于活动状态,升级为轻量级锁的状态。

3、轻量级锁

轻量级锁: 是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁(A线程没有执行完同步代码块时,线程B就来竞争锁),线程 B会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。

轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

轻量级锁转为重量级锁:
当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

偏向锁、轻量级锁、重量级锁之间的转换:
在这里插入图片描述
1)一开始如果偏向锁可用,就成为偏向锁(一个线程持有锁);也就是没有人竞争的情况下就为偏向锁。
2)如果有其他线程来竞争,偏向锁就转为轻量级锁
3)如果轻量级锁处理不了就会转为重量级锁

4、锁消除

很多应用程序的代码,用到了synchronized锁,但其实没在多线程环境下。这些加锁——释放锁的操作统统变得没有意义了。优化的时候就把锁消除掉!

锁消除:锁消除即删除不必要的加锁操作。一般使用在方法内部的局部变量,不存在多个线程共享问题。

下面来看一个例子:
在这里插入图片描述
变量sb是使用在自己内部,为main线程里的一个局部变量,所以该局部变量不可能被其他线程使用,所以三个append操作不需要加锁都是安全的。

上面代码中并不涉及多线程的情况下,使用了StringBuffer,我们知道StringBuffer是线程安全的,使用了synchronized关键字,所以此时并没有提高效率调用一次append方法获取释放锁,降低了效率。在这种情况下,就不需要使用锁,就采用了锁消除,将锁干掉。

5、锁粗化

锁粗化:锁粗化就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁。一般是用在类变量上因为可能有好几个方法调用(线程)
在这里插入图片描述
这里涉及到方法区里,类里边有共享变量(sb为类变量,是共享的),所以不能保证该变量有可能被其他线程在使用,因此,将三个append方法的加锁解锁合并为一个加锁解锁操作,即使有其他线程在使用,合并成为一个加锁解锁操作后也需要竞争锁,保证不受影响。

synchronized 优化总结:

1.编译器+JVM 判断锁是否可以被消除,如果可以,直接消除;

2.第一个线程,优先进入偏向锁状态,该线程的加锁-释放锁操作,非常快;

3.随着其他线程参与竞争,偏向锁状态被消除,进入轻量级锁(用户态锁),用户态锁中加入了智能的自旋状态(空转);

4.如果一定条件满足不了轻量级锁就会膨胀为重量级锁(OS: :mutex),重量级锁是操作系统实现的,效率比较低,成本较高。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值