聊聊 Java 的几把 JVM 级锁

  • 时间:
  • 浏览:1

     存储hashCode、GC分代年龄、锁类型标记、偏向锁应用守护进程ID、CAS锁指向应用守护进程LockRecord的指针等,synconized锁的机制与一些每种(markwork)密切相关,用markword中最低的三位代表锁的清况 ,其中一位是偏向锁位,另外两位是普通锁位

      对象指向它的类元数据的指针、JVM一些一些 通过它来选用是哪个Class的实例

(2)实例数据区域 此处存储的是对象真正有效的信息,比如对象中所有字段的内容

ReentrantLock**ReentrantLock从字面都可以看出是一把可重入锁,这点和synchronized一样,但实现原理也与syncronized有很大差别,它是基于经典的AQS(AbstractQueueSyncronized)实现的,AQS是基于volitale和CAS实现的,其中AQS中维护有二个 valitale类型的变量state来做有二个 可重入锁的重入次数,加锁和释放锁也是围绕一些变量来进行的。ReentrantLock也提供了一些synchronized这麼的特点,之前 比synchronized好用

AQS模型如下图:

a、WriteLock(写锁)是悲观锁(排他锁、互斥锁)

通过计算 state&((1<<16)-1),将state的高16位全版抹去,之前 state的低位记录着写锁的重入计数

升级到轻量级锁当下有二个 应用守护进程参与到偏向锁竞争时,会先判断markword中保存的应用守护进程ID与非 与一些应用守护进程ID相等,导致 不相等,会立即撤出 偏向锁,升级为轻量级锁。每个应用守护进程在此人 的应用守护进程栈中生成有二个 LockRecord(LR),之前 每个应用守护进程通过CAS(自旋)的操作将锁对象头中的markwork设置为指向此人 的LR的指针,哪个应用守护进程设置成功,就导致 获得锁。关于synchronized中此时执行的CAS操作是通过native的调用HotSpot中bytecodeInterpreter.cpp文件C++代码实现的,有兴趣的都可以继续深挖

b、可重入

synchronized拥有强制原子性的实物锁机制,是一把可重入锁。之前 ,在有二个 应用守护进程使用synchronized妙招 时调用该对象原先synchronized妙招 ,即有二个 应用守护进程得到有二个 对象锁后再次请求该对象锁,是永远都可以拿到锁的。在Java中应用守护进程获得对象锁的操作是以应用守护进程为单位的,而总要以调用为单位的。synchronized锁的对象头的markwork中会记录该锁的应用守护进程持有者和计数器,当有二个 应用守护进程请求成功后,JVM会记下持有锁的应用守护进程,并将计数器计为1。此时一些应用守护进程请求该锁,则都要等待。而该持有锁的应用守护进程导致 再次请求一些锁,就都可以再次拿到一些锁,一起计数器会递增。当应用守护进程退出有二个 synchronized妙招 /块时,计数器会递减,导致 计数器为0则释放该锁锁。

c、悲观锁(互斥锁、排他锁)

synchronized是一把悲观锁(独占锁),当前应用守护进程导致 获取到锁,会导致 其它所有都要锁该的应用守护进程等待,老会 等待持有锁的应用守护进程释放锁才继续进行锁的争抢

如上图所示,synchronized锁升级的顺序为: 偏向锁->轻量级锁->重量级锁,每一步触发锁升级的清况 如下:

偏向锁在JDK1.8中,嘴笨 默认是轻量级锁,但导致 设定了-XX:BiasedLockingStartupDelay = 0,那在对有二个 Object做syncronized的以前,会立即上一把偏向锁。当处在偏向锁清况 时,markwork会记录当前应用守护进程ID

ReentrantLock有如下特点:

a、可重入     ReentrantLock和syncronized关键字一样,总要可重入锁,不过两者实现原理稍有差别,RetrantLock利用AQS的的state清况 来判断资源与非 已锁,同一应用守护进程重入加锁,state的清况 +1; 同一应用守护进程重入解锁,state清况 -1(解锁都要为当前独占应用守护进程,之前 异常); 当state为0时解锁成功。

synchronized

     synchronized关键字是一把经典的锁,也是亲戚亲戚当让我们平时用得最多的。在jdk1.6以前,syncronized是一把重量级的锁,不过随着jdk的升级,也在对它进行不断的优化,如今它变得不这麼重了,甚至在一些场景下,它的性能反而优于轻量级锁。在加了syncronized关键字的妙招 、代码块中,一次只允许有二个 应用守护进程进入特定代码段,从而防止多应用守护进程一起修改同一数据。

synchronized锁有如下十几个 特点:

(3)对齐填充区域

 JVM的实现HostSpot规定对象的起始地址都很久 8字节的整数倍,换句话来说,现在64位的OS往外读取数据的以前一次性读取64bit整数倍的数据,也一些一些 8个字节,一些一些HotSpot为了高效读取对象,就做了"对齐",导致 有二个 对象实际占的内存大小总要8byte的整数倍时,就"补位"到8byte的整数倍。一些一些对齐填充区域的大小总要固定的。

LongAdder

在高并发的清况 下,亲戚亲戚当让我们对有二个 Integer类型的整数直接进行i++的以前,无法保证操作的原子性,会老出应用守护进程安全的大问提。为此亲戚亲戚当让我们会用juc下的AtomicInteger,它是有二个 提供原子操作的Interger类,实物也是通过CAS实现应用守护进程安全的。但当小量应用守护进程一起访问时,就会导致 小量应用守护进程执行CAS操作失败而进行空旋转,导致 CPU资源消耗无需 ,之前 执行波特率一些一些 高。Doug Lea大神应该一些一些 满意,于是在JDK1.8中对CAS进行了优化,提供了LongAdder,它是基于了CAS分段锁的思想实现的。

应用守护进程去读写有二个 LongAdder类型的变量时,流程如下:

c、支持设置锁的超时时间   synchronized关键字无法设置锁的超时时间,导致 有二个 获得锁的应用守护进程实物处在死锁,这麼一些应用守护进程就会老会 进入阻塞清况 ,而ReentrantLock提供tryLock妙招 ,允许设置应用守护进程获取锁的超时时间,导致 超时,则跳过,不进行任何操作,防止死锁的处在

如上图所示,在创建有二个 对象后,在JVM虚拟机(HotSpot)中,对象在Java内存中的存储布局 可分为三块:

**(1)对象头区域

b、ReadLock(读锁)是共享锁(乐观锁)

通过计算 state>>>16 进行无符号补0,右移16位,之前 state的高位记录着写锁的重入计数

 读锁获取锁的过程比写锁稍微错综复杂些,首先判断写锁与非 为0之前 当前应用守护进程不占有独占锁,直接返回;之前 ,判断读应用守护进程与非 都要被阻塞之前 读锁数量与非 小于最大值之前 比较设置清况 成功,若当前这麼读锁,则设置第有二个 读应用守护进程firstReader和firstReaderHoldCount;若当前应用守护进程应用守护进程为第有二个 读应用守护进程,则增加firstReaderHoldCount;之前 ,将设置当前应用守护进程对应的HoldCounter对象的值,更新成功总要在firstReaderHoldCount中readHolds(ThreadLocal类型的)的本应用守护进程副本中记录当前应用守护进程重入数,这是为了实现jdk1.6中加入的getReadHoldCount()妙招 的,一些妙招 能获取当前应用守护进程重入共享锁的次数(state中记录的是多个应用守护进程的总重入次数),加入了一些妙招 让代码错综复杂了不少,之前 其原理还是很简单的:导致 当前只能有二个 应用守护进程得话,还不都要动用ThreadLocal,直接往firstReaderHoldCount一些成员变量里存重入数,当有第5个应用守护进程来的以前,就要动用ThreadLocal变量readHolds了,每个应用守护进程拥有此人 的副本,用来保存此人 的重入数。

作者信息:

夏杰 ,花名楚昭,现就职于阿里巴巴企业智能事业部 BUC&ACL&SSO 团队,面向阿里巴巴经济体提供人员账号的权限管控、应用数据安全访问治理,并通过现有的技术沉淀与领域模型,致力于打造 To B、To G 领域的应用信息化架构的基础设施 SAAS 产品 MOZI 。

释放写锁源码:

当应用守护进程进入到synchronized处尝试获取该锁时,synchronized锁升级流程如下:

**此处存储的信息包括两每种:

e、可中断锁   ReentrantLock中的lockInterruptibly()妙招 使得应用守护进程都可以在被阻塞时响应中断,比如有二个 应用守护进程t1通过lockInterruptibly()妙招 获取到有二个 可重入锁,并执行有二个 长时间的任务,原先应用守护进程通过interrupt()妙招 就都可以立刻打断t1应用守护进程的执行,来获取t1持有的那个可重入锁。而通过ReentrantLock的lock()妙招 导致 Synchronized持有锁的应用守护进程是无需响应一些应用守护进程的interrupt()妙招 的,直到该妙招 主动释放锁以前才会响应interrupt()妙招 。

d、支持公平/非公平锁   synchronized关键字是五种非公平锁,先抢到锁的应用守护进程先执行。而ReentrantLock的构造妙招 中允许设置true/false来实现公平、非公平锁,导致 设置为true,则应用守护进程获取锁要遵循"先来后到"的规则,每次总要构造有二个 应用守护进程Node,之前 到双向链表的"尾巴"上端排队,等待前面的Node释放锁资源。

b、都要手动加锁、解锁     synchronized关键字是自动进行加锁、解锁的,而ReentrantLock都要lock()和unlock()妙招 配合try/finally得话块来完成,来手动加锁、解锁

升级到重量级锁导致 锁竞争加剧(如应用守护进程自旋次数导致 自旋的应用守护进程数超过某阈值,JDK1.6以前,由JVM此人 控制改规则),就会升级为重量级锁。此时就会向操作系统申请资源,应用守护进程挂起,进入到操作系统内核态的等待队列中,等待操作系统调度,之前 映射回用户态。在重量级锁中,导致 都要做内核态到用户态的转换,而一些过程中都要消耗较多时间,也一些一些 "重"的导致 之一。

通过分析都可以看出:

在应用守护进程持有读锁的清况 下,该应用守护进程只能取得写锁(导致 获取写锁的以前,导致 发现当前的读锁被占用,就马上获取失败,不管读锁是总要被当前应用守护进程持有)。

在应用守护进程持有写锁的清况 下,该应用守护进程都可以继续获取读锁(获取读锁时导致 发现写锁被占用,只能写锁这麼被当前应用守护进程占用的清况 才会获取失败)。

a、有锁升级过程

     在jdk1.5(含)以前,synchronized的底层实现是重量级的,一些一些以前一致称呼它为"重量级锁",在jdk1.5以前,对synchronized进行了各种优化,它变得不这麼重了,实现原理一些一些 锁升级的过程。亲戚亲戚当让我们先聊聊1.5以前的synchronized实现原理是如何的。说到synchronized加锁原理,就不得不先说java对象在内存中的布局,java对象内存布局如下:

ReentrantReadWriteLock

ReentrantReadWriteLock(读写锁)嘴笨 是两把锁,一把是WriteLock(写锁),一把是读锁,ReadLock。读写锁的规则是:读读不互斥、读写互斥、写写互斥。在一些实际的场景中,读操作的频率远远高于写操作,导致 直接用一般的锁进行并发控制得话,就会读读互斥、读写互斥、写写互斥,波特率低下,读写锁的产生一些一些 为了优化一些场景的操作波特率。一般清况 下独占锁的波特率低来源于高并发下对临界区的激烈竞争导致 应用守护进程上下文切换。之前 当并发总要很高的清况 下,读写锁导致 都要额外维护读锁的清况 ,导致 还不如独占锁的波特率高。之前 都要根据实际清况 选用使用。ReentrantReadWriteLock的原理也是基于AQS进行实现的,与ReentrantLock的差别在于ReentrantReadWriteLock锁拥有共享锁、排他锁属性。读写锁中的加锁、释放锁也是基于Sync(继承于AQS),之前 主要使用AQS中的state和node中的waitState变量进行实现的。实现读写锁与实现普通互斥锁的主要区别在于都要分别记录读锁清况 及写锁清况 ,之前 等待队列中都要区别防止五种加锁操作。ReentrantReadWriteLock中将AQS中的int类型的state分为高16位与第16位分别记录读锁和写锁的清况 ,如下图所示:

获取读锁源码: 

获取写锁源码:

简介

       在计算机行业有有二个 定律叫"摩尔定律",在此定律下,计算机的性能突飞猛进,之前 价格也随之这麼便宜,cpu从单核到了多核,缓存性能也得到了很大提升,尤其是多核cpu技术的到来,计算机同一时刻都可以防止多个任务。在硬件层面的发展带来的波特率极大提升中,软件层面的多应用守护进程编程导致 成为必然趋势,然而多应用守护进程编程就会引入数据安全性大问提,有矛必有盾,于是发明权人了“锁”来防止应用守护进程安全大问提。在这篇文章中,总结了java中几把经典的JVM级别的锁。

LongAdder也是基于Unsafe提供的CAS操作+valitale去实现的。在LongAdder的父类Striped64中维护着有二个 base变量和有二个 cell数组,当多个应用守护进程操作有二个 变量的以前,先会在一些base变量上进行cas操作,当它发现应用守护进程增多的以前,就会使用cell数组。比如当base将要更新的以前发现应用守护进程增多(也一些一些 调用casBase妙招 更新base值失败),这麼它会自动使用cell数组,每有二个 应用守护进程对应于有二个 cell,在每有二个 应用守护进程中对该cell进行cas操作,原先就都可以将单一value的更新压力分担到多个value中去,降低单个value的 “热度”,一起也减少了应用守护进程小量应用守护进程的空转,提高并发波特率,分散并发压力。一些分段锁都要额外维护有二个 内存空间cells,不过在高并发场景下,这点成本几乎都可以忽略。分段锁是五种优秀的优化思想,juc中提供的的ConcurrentHashMap也是基于分段锁保证读写操作的应用守护进程安全。

释放读锁源码: