Java synchronize底层实现原理及优化

 更新时间:2020年03月18日 14:32:46   作者:淡定一生2333  
这篇文章主要介绍了Java synchronize底层实现原理及优化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

首先来说下synchronize和Lock的区别:

两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别:

  • synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代码不执行,所以Lock一般在Finally中释放,而synchronize释放锁是由JVM自动执行的。
  • Lock有共享锁的概念,所以可以设置读写锁提高效率,synchronize不能。(两者都可重入)
  • Lock可以让线程在获取锁的过程中响应中断,而synchronize不会,线程会一直等待下去。lock.lockInterruptibly()方法会优先响应中断,而不是像lock一样优先去获取锁。
  • Lock锁的是代码块,synchronize还能锁方法和类。
  • Lock可以知道线程有没有拿到锁,而synchronize不能

Lock锁对应有源码的,可以查看下代码,那么synchronize在JVM层面是怎么实现的呢,我们看下字节码文件:

先用javac Test.class 编译出class文件再用javap –c Test.class查看字节码文件

我们写个DEMO看下,JVM底层是怎么实现synchronized的:、

public class Test4 {

  private static Object LOCK = new Object();
  
  public static int main(String[] args) {
    synchronized (LOCK){
      System.out.println("Hello World");
    }
    return 1;
  }
}

在看下上面代码对应的字节码

也就是说,锁是通过monitorenter和monitorexit来实现的,这两个字节码代表的是啥意思:

可以在下面参考的网页中了解monitorenter和monitorexit的作用,我就不盗用他们的话了,大致意思是,每个对象都有一个monitor监视器,调用monitorenter就是尝试获取这个对象,成功获取到了就将值+1,离开就将值减1。如果是线程重入,在将值+1,说明monitor对象是支持可重入的。

我之前分析过一篇ReenternLock,概念都是类似的,只是锁是自身维护了一个volatile int类型的变量,通过对它加一减一表示占有锁啊重入之类的概念。

注意,如果synchronize在方法上,那就没有上面两个指令,取而代之的是有一个ACC_SYNCHRONIZED修饰,表示方法加锁了。它会在常量池中增加这个一个标识符,获取它的monitor,所以本质上是一样的。

HotSpot中锁的具体实现以及对它的优化:

重量级锁:

最基础的实现方式,JVM会阻塞未获取到锁的线程,在锁被释放的时候唤醒这些线程。阻塞和唤醒操作是依赖操作系统来完成的,所以需要从用户态切换到内核态,开销很大。并且monitor调用的是操作系统底层的互斥量(mutex),本身也有用户态和内核态的切换,所以JVM引入了自旋的概念,减少上面说的线程切换的成本。

自旋锁:

如果锁被其他线程占用的时间很短,那么其他获取锁的线程只要稍微等一下就好了,没必要进行用户态和内核态之间的切换,等的状态就叫自旋。例如如下代码:

public class SpinLock {
  private AtomicReference<Thread> cas = new AtomicReference<Thread>();
  public void lock() {
    Thread current = Thread.currentThread();
    // 利用CAS,获取值不对则无限循环
    while (!cas.compareAndSet(null, current)) {
      // DO nothing
    }
  }
  public void unlock() {
    Thread current = Thread.currentThread();
    cas.compareAndSet(current, null);
  }
}

自旋会跑一些无用的CPU指令,所以会浪费处理器时间,如果锁被其他线程占用的时间段的话确实是合适的…如果长的话就不如使用直接阻塞了,那么JVM怎么知道锁被占用的时间到底是长还是短呢?

因为JVM不知道锁被占用的时间长短,所以使用的是自适应自旋。就是线程空循环的次数时会动态调整的。

可以看出,自旋会导致不公平锁,不一定等待时间最长的线程会最先获取锁。

轻量级锁:

JDK1.6之后加入,它的目的并不是为了替换前面的重量级锁,而是在实际没有锁竞争的情况下,将申请互斥量这步也省掉。锁实现的核心在与对象头(MarkWord)的结构,对象自身会有信息表示所有被锁住并且锁是什么类型,如下所示:

如果代码进入同步块时,检测到对象未锁定,即标志位为01。那么当前线程就会在自身栈帧中建议一个区域保存对象的MarkWord信息,再使用CAS的方式让这个区域指向对象的MarkWork区域,这样就算加上锁了。(这样就没有获取系统mutex变量,只是改了个值,但是如果有竞争的话,就要升级成重量级锁,这样反倒变慢了)

加锁前VS 加锁后:

偏向锁:

比轻量级锁更绝,将同步操作全部省略…设置步骤是和前面的轻量级锁一样的,不同的是标志位设置的是01,即偏向模式。

不同的是同一个线程第二次进来之后,虚拟机不会再进行任何的同步操作,比如Mark Word的update。

如果有其他线程来,偏向模式就结束了,标志位会恢复到未锁定或者偏向锁。所以如果锁总是会被多个线程访问的话,还是禁止掉偏向锁优化比较好。

锁优化流程如下:(出自周志明老师的那本讲解JVM的书)

可以看出,锁是一个逐步升级的过程,不会一开始上来就重量级锁。锁一般只会升级不会降级,避免降级之后冲突导致效率不行并且又得升级。但是降级其实是允许的(STW的时候),可以看下参考中文章里面提到的英文网站。

其他的优化还有锁消除以及锁粗化:

如果一段代码其实在作用域可以不加锁的,Javac编译器会自动优化。

锁粗化是指代码在一段代码中多次加锁,会被JVM优化成对整个代码段加锁。

(但是这两点是JVM对代码的优化,而不是对synchronized优化了,这里只是顺带提一下)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Spring中的refresh方法分析

    Spring中的refresh方法分析

    这篇文章主要介绍了Spring中的refresh方法分析,文章围绕主题展开详细的refresh方法相关资料介绍,需要的小伙伴可以参考一下
    2022-05-05
  • Java+Swing实现医院管理系统的完整代码

    Java+Swing实现医院管理系统的完整代码

    这篇文章主要介绍了Java+Swing实现医院管理系统的完整代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • 详解JUC并发编程中的进程与线程学习

    详解JUC并发编程中的进程与线程学习

    这篇文章主要为大家详细介绍了JUC并发编程中的进程与线程学习,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Java从零编写吃货联盟订餐系统全程讲解

    Java从零编写吃货联盟订餐系统全程讲解

    这篇文章主要介绍了Java订餐系统,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-12-12
  • 详解使用Spring快速创建web应用的两种方式

    详解使用Spring快速创建web应用的两种方式

    这篇文章主要介绍了详解使用Spring快速创建web应用的两种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • SpringBoot使用GTS的示例详解

    SpringBoot使用GTS的示例详解

    这篇文章主要介绍了SpringBoot使用GTS的示例详解,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-10-10
  • java web请求和响应中出现中文乱码问题的解析

    java web请求和响应中出现中文乱码问题的解析

    这篇文章主要为大家解析了java web请求和响应中出现中文乱码问题,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • Java中instanceof 关键字的使用

    Java中instanceof 关键字的使用

    instanceof通过返回一个布尔值来指出,某个对象是否是某个特定类或者是该特定类的子类的一个实例,本文就来详细的介绍一下instanceof 关键字的使用,感兴趣的可以了解一下
    2023-10-10
  • maven工程中读取resources中的资源文件

    maven工程中读取resources中的资源文件

    Web项目中应该经常有这样的需求,在maven项目的resources目录下放一些文件,比如一些配置文件,资源文件等,本文主要介绍了maven工程中读取resources中的资源文件,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Spring-Security实现登录接口流程

    Spring-Security实现登录接口流程

    Security 是 Spring 家族中的一个安全管理框架,SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器,这篇文章主要介绍了Spring-Security实现登录接口,需要的朋友可以参考下
    2023-05-05

最新评论