并发编程(八)synchronized关键字解析

一、背景分析

设计同步机制的意义

  多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源这种资源可能是:对象、变量、文件等。

  • 共享资源可以由多个线程同时访问
  • 可变资源可以在其生命周期内被修改

  引出的问题:由于线程执行的过程是不可控的,所以需要采用同步机制来控制对象的访问!

如何解决线程并发安全问题?

  解决方案:序列化访问临界资源【即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。】

  Java 中,提供了两种方式来实现同步互斥访问:

  • synchronized【内置锁,隐式锁】
  • Lock【显式锁】

  PS:同步机制的本质就是加锁

  加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源【同步互斥访问】

  PS:当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,是不会导致线程安全问题的。

技术发展背景

二、synchronized原理详解

  定义:synchronized内置锁是一种对象锁锁的是对象而非引用,作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的

  加锁的方式:

  • 同步实例方法,锁的是当前实例对象【new 出来的对象上】(加锁粒度较细)
  • 同步静态方法【类方法】,锁的是当前类对象【XXX.class】(加锁粒度较广)
  • 同步代码块,锁的是括号里面的对象

PS:若一个类中有多于1个的静态方法设置了Synchronized关键字时,会非常影响性能,因为这两个Synchronized关键字相当于都所在同一个class对象上【比如用Spring容器管理的话,这两个方法锁的是同一个bean】

PS:不要不要在项目中写System.out.printIn(),因为这个底层加了synchronized关键字,且为单例,那么单例就意味着所有存在System.out.printIn()语句的地方锁的都是同一个对象!

synchronized底层原理

  synchronized是基于JVM内置锁实现,通过内部对象Monitor【监视器锁】实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock【互斥锁】实现,它是一个重量级锁性能较低。经优化后内置锁的并发性能已经基本与Lock持平。 synchronized关键字被编译成字节码后会被翻译成 monitorenter monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置

  PS:synchronized的语义底层是通过一个Monitor的对象来完成,所以只有在同步代码块或方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常【对象的监视器状态为不是需要同步的场景】

  每个同步对象都有一个自己的Monitor【监视器锁】,加锁过程如下图所示:

三、Monitor监视器锁

   任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。

monitor监视器锁核心指令

monitorenter

  每个对象可以成为一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
  • 2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。【可重入】
  • 3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit

  PS:执行monitorexit的线程必须是objectref所对应的monitor的所有者。

  指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

什么是Monitor?

  可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象

  与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中,每一个Java对象自产生开始就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

四、锁的膨胀升级

  锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。

  随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

  从JDK 1.6 中默认是开启偏向锁和轻量级锁的,可以通过-XX:-UseBiasedLocking来禁用偏向锁。

偏向锁

  产生的背景:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁的代价而引入偏向锁。【获取锁会涉及到一些CAS操作比较耗时】

  核心思想:如果一个线程获得了🔒,那么🔒就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求🔒时,无需再做任何同步操作,这样就省去了大量有关锁申请的操作,提搞程序的性能。

  PS:🔒竞争不激烈的时候偏向锁对性能提升很显著,但是🔒竞争很激烈时,偏向锁对性能提升不大了,就会再向上升级以满足同步线程所需要的资源开销。

默认开启偏向锁 
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 
关闭偏向锁:-XX:-UseBiasedLocking

轻量级锁

  产生的背景:偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。

  适应场景:线程交替执行同步块,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

自旋锁

  产生的背景:轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。

  解析:这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,因此自旋锁会假设在不久将来,当前的线程可以获得锁,虚拟机会让当前想要获取锁的线程做几个空循环【这也是称为自旋的原因】,一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。

  如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的,最后没办法也就只能升级为重量级锁了。

锁消除

  锁消除是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,即消除没有必要的锁,可以节省毫无意义的请求锁时间

  举个🌰:

public void setString(String key) {
   //1、方法内创建一个StringBuffer的对象
   StringBuffer buffer = new StringBuffer();
   //2、buffer.append()是同步方法【但由于str变量是局部变量,并且不会被其他线程所使用,这种情况JVM会自动把锁消除】
   String str = buffer.append("").toString();
}

  锁消除的依据是逃逸分析的数据支持

  锁消除,前提是java必须运行在server模式(server模式会比client模式作更多的优化),同时必须开启逃逸分析 :

  • -XX:+DoEscapeAnalysis 开启逃逸分析
  • -XX:+EliminateLocks 表示开启锁消除

五、逃逸分析

  定义:分析对象动态作用域。当一个对象在方法中被定义后,分析它是否被外部方法所引用,例如作为调用参数传递到其他地方中。

  使用逃逸分析,编译器可以对代码做如下优化:

  • 同步省略 如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。【对象不会逃逸则可以不考虑同步的问题】
  • 将堆分配转化为栈分配 :如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配,而不是堆分配。【栈内存对象】
  • 分离对象或标量替换 :有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中【标量替换的概念】
posted @ 2021-07-12 17:41  有梦想的肥宅  阅读(81)  评论(0编辑  收藏  举报