# Synchronized 原理和优化

1 篇文章 0 订阅

synchronized 原理和优化

synchronized 是java 中解决并发问题最常用的方法,也是最简单的一种方法。Synchronized 的主要作用是

  1. 确保线程互斥访问同步代码
  2. 保证共享变量的修改能及时可见
  3. 有效解决重排问题

Synchronized 原理

反编译下面代码看看Synchronized 是如何实现代码同步的

public class Demo{
	public void method(){
		sychronized(this){
			System.out.println("start");
		}
	}
}

反编译(javap -verbose Demo.class)结果

  stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String start
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

关于 monitorenter 和 monitorexit俩条指令的作用,参考JVM 规范

Java 虚拟机中的同步(synchronization) 基于进入和退出monitor对象实现,无论是显示同步(有明确的monitorenter和 monitorexit指令,即同步代码块)还是隐式同步都是如此。在java 语言中,同步用的最多的地方可能是synchronzied 修饰的同步方法。同步方法并不是由monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法表结构的 ACC_SYNCHRONIZED 标志来隐式实现的。

同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何一个兑现都有一个monitor与之关联,当且一个monitor被持有之后,他将处于被锁定状态。线程执行到monitorenter 指令时,将会获取对象所对应的monitor 所有权,即尝试获取对象的锁。

在jvm 中对象在内存中分为三块区域:对象头、实例变量和填充数据

实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这点了解即可。

对象头:Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。

Mark Word:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。

Monitor: 我们可以把它理解为一个同步工具,也可是一种同步机制,通常被描述为一个对象。与一切对象一样,所有的Java 对象自带Monitor,每一个Java 对象都有成为Monitor 的潜质,因为在Java的设计中,每一个Java 对象都有一把内部(Monitor)锁。Monitor 是线程私有的数据结构,每一个线程都有一个可用Monitor record 列表,同时还有一个全局可用列表。每一个被锁住的对象会和monitor 关联,同时Monitor中有一个Owner 字段存放该锁的线程的唯一标识,表示该所被线程占用。结构如下:

在这里插入图片描述

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL

EntryQ: 关联一个系统互斥锁(semaphore),阻塞所有试图monitor record 失败线程

RcThis: 表示blocked 或waiting 在该monitor record 上所有线程的个数

Nest: 用来实现重入锁的计数

HashCode: 保存从对象头拷贝过来的HashCode值

Candidate: 为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

Java 虚拟即对synchronized 的优化

在这里插入图片描述

锁的状态总共四种,无锁状态、偏向锁、轻量级锁、重量级锁。随着锁的竞争可以冲偏向锁升级到轻量级锁,再次升级为重量级锁。锁升级是单向的,也就是说只能从低到高升级,不会出现锁降级。

偏向锁

偏向锁是jdk 6 之后引入的新锁,是对加锁操作的一种优化。大多数情况下,所不仅不存在多线程的竞争,而且总是有同一线程多次获得,因此为了减少同一线程获取锁(会涉及到CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得锁,那么锁进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。

轻量级锁

若偏向锁失败,虚拟机不会立马升级为重量级锁,它还会尝试使用一种轻量级锁的优化手段。此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

自旋锁

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。

锁消除

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

synchronize的可重入性:

从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值