synchronized关键字实现原理

目录

概述

底层实现

监视器(monitor)

锁升级 

偏向锁

轻量级锁

与偏向锁的区别

轻量级锁的加锁过程

轻量级锁的解锁过程

重量级锁

整体流程图


概述

synchronized是Java内建的同步机制,它提供了互斥的语义和可见性,我们对它的普遍理解就停留在了: 当一个线程已经获取当前锁时,其他试图获取同步锁的线程只能阻塞在那里。

这样的认知其实也不算是错的,但是它并没有我们想象中的那么凶狠,直接就给你上操作系统的互斥锁,如果不考虑线程的数量,以及是否并发,一味的使用操作系统分配的资源,那么这样必然会造成大量的资源浪费,其实它也有自己温柔的一面(在JDK1.6以后进行了优化)

在Java 5 以前,synchronized是仅有的线程同步手段。在代码中,它可以用来修饰方法,也可以用在特定的代码块上,本质上都是将指定代码块包围起来,达到线程安全的效果。

底层实现

其实我们使用synchronized来实现线程安全,也就是为了保证原子性,就是说要保护的操作必须具备原子性(同一时间只能由一个线程执行,其他线程只能等当前线程结束后再进行操作)。

这里不再对线程不安全的情况进行详细阐述,导致不安全的原因只有一个: 就是某个线程任务在同一时间被多个线程所执行,导致每次更改后的结果不能同步变化。

解决办法: 将某个任务用synchronized 保护起来,前提是各个线程之间要共用一把锁,就可以避免多个线程并发修改某个公共变量。

我们在对一个加了synchronized关键字的操作进行反编译时可以发现,它是通过monitorenter/monitorexit实现了同步的语义。所以,synchronized代码块是由一对monitorenter/monitorexit指令实现,synchronized是通过对象内部的叫做监视器(monitor)来实现的,线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

监视器(monitor)

在JVM实现规范中关于monitor的描述: 每个对象都有一个监视器,线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

获取monitor所有权的大致过程:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程为monitor的所有者,代表持有锁;
  • synchronized是一种可重入锁,如果持有锁的线程重新进入锁,则将进入数+1;
  • 其他竞争此锁的线程进入阻塞状态(blocked),它们被存放在monitor中的EntryList中。直到进入数为0,再重新尝试获取monitor的所有权;
  • 此前,可能有些线程被monitor执行了wait()方法,使其进入等待状态(waiting),他们被存放在WaitSet中,等待唤醒后则继续竞争。(终于理解了为什么是当前对象调用wait()方法使当前线程进入等待,因为当前对象是monitor,由monitor进行管理);
  • 竞争到的线程,monitor中的owner区域会指向它。

锁升级 

在Java 6之前,synchronized的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,非常消耗系统资源。

在JDK1.6之后,synchronized进行了优化,它不会再一味的对操作加上重量级锁,而是会通过具体的情况对锁进行不同程度的设计,提供了三种不同的Monitor实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

所谓锁的升级、降级,就是JVM优化synchronized运行的机制,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。

偏向锁

核心思想: 假设加锁的代码从始至终都只有一个线程在调用,如果发现多于一个线程在调用,再升级为轻量级锁。

偏向锁是为了在单线程(没有出现并发)的情况下 ,尽量减少不必要的轻量级锁的执行路径,当某个线程第一次进入锁时,会将当前线程记录在monitor的Thread Id区域中,在后续访问中便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。因为轻量级锁的加锁与释放锁,也需要多次执行CAS原子指令。而偏向锁只需要切换Thread Id时执行一次CAS指令。所以,偏向锁的作用是在单线程中进一步提高性能。

当没有线程并发出现时,默认会使用偏斜锁。JVM会利用CAS操作(compare and swap),在对象头上的Mark Word部分设置Thread ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

轻量级锁

概念: 它相对于"使用操作系统互斥锁来实现的重量级锁",但它并不能代替重量级锁,它的本意是在没有并发情况下,减少重量级锁产生的性能消耗。轻量级锁适应的场景是线程交替执行同步代码块的情况下,一旦出现并发,则升级为重量级锁。

虽然轻量级锁不支持并发,但是支持多线程以串行的方式访问同步代码块。但是,每次执行需要加锁和解锁,都会消耗重复加锁解锁的性能开销。

与偏向锁的区别

  • 偏向锁不允许多个线程对它进行竞争,因为它不存在加锁和解锁的动作,只是单纯的记录Thread Id,除非偏向的线程执行完毕,或者销毁,总之要保证从始至终都只有一个线程在调用;
  • 轻量级锁允许多个线程串行调用,因为它有加锁和解锁的动作,但不支持并发情况。

在Hotspot虚拟机中,一个JAVA对象的存储结构,在内存中的存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding),锁的状态保存在对象头中。 

  

  • lock标志位:2位二进制,锁状态标记位。
  • ageJava对象年龄:在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。
  • thread:持有偏向锁的线程ID。
  • ptr_to_lock_record:指向栈中锁记录的指针。 

轻量级锁的加锁过程

  1. 在代码进入同步代码块的时候,如果对象锁状态为无锁状态(lock标志位“01”,biased_lock标志位“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方命名为Displaced Mark Word。
  2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中。
  3. 拷贝成功后,虚拟机将尝试将对象的Mark Word中的ptr_to_lock_record更新为指向Lock Record的指针,并将Lock record里的owner指针指向到对象的Mark Word。如果更新成功,则执行步骤4,否则执行步骤5。
  4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的lock标志位设置为“00”,即表示此对象处于轻量级锁定状态。
  5. 如果操作失败,虚拟机首先会检查对象的Mark Word是否已经指向当前线程的lock record。如果是,就说明该线程已经拥有了这个对象的锁,那就可以直接进入同步代码块中继续执行,否则说明多个线程竞争同一把锁,那么此时要升级为重量级锁,lock标志位的状态值变为"10",对应Mark Word中存储的就是ptr_to_heavyweight_monitor(指向重量级锁的指针),其他竞争该锁的线程则进入阻塞状态。当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

轻量级锁的解锁过程

  1. 通过CAS指令,尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
  2. 如果替换成功,整个同步过程就完成了。
  3. 如果替换失败,说明有其他线程尝试过获取该锁,该锁已升级为重量级锁,那就要在释放锁的同时,通知其它线程重新参与锁的竞争。

重量级锁

依赖于操作系统互斥锁(Mutex Lock)所实现的锁。操作系统的互斥锁实现线程之间的切换,需要从用户态转换到核心态,切换成本非常高,状态之间的转换需要相对比较长的时间,这是早期Synchronized效率低的原因。因此,这种依赖于操作系统互斥锁(Mutex Lock)所实现的锁,称之为“重量级锁”。

用户态和内核态,代表两种不同的CPU状态。内核态(Kernel Mode)用于运行操作系统程序,用户态(User Mode) 用于运行用户程序。

整体流程图

image.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值