t2与t1竞争,偏向锁升级为轻量锁,11111000后三位是000 打印t2线程,第20次偏向结果 A object internals: OFFSET SIZE TYPE DESCRIPTION 前面19个a都偏向锁升级为轻量锁,达到重偏向阈值,第20个a直接重偏向到t2 打印list中第11个对象的对象头: A object internals: OFFSET SIZE TYPE t2竞争了前30个对象a,第0~18个对象a未达到重偏向阈值,是轻量锁,待走出t2同步块后进入无锁状态。第19~29发生重偏向,是偏向锁。 此时经过t2竞争前40个a,已经到了批量撤销的阈值40,而第20~39已经重偏向过t2,不会再次重偏向,所以升级为轻量锁。(偏向锁重偏向一次之后不可再次重偏向。) JVM 看到类A已经经历过批量撤销,剥夺了A的新实例对象使用偏向锁的权利;第50个对象a始终没有被t2 t3竞争过,还是偏向t1。(批量重偏向和批量撤销是针对类的优化,和对象无关。)
今天简单了解了一下java轻量级锁和重量级锁以及偏向锁。 偏向锁 偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。 偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。 偏向所锁,轻量级锁及重量级锁 偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。 一个对象刚开始实例化的时候,没有任何线程来访问它的时候。 ,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。
用2字(32位JVM中1字=32bit=4baye)存储对象头,如果是数组类型使用3字存储(还需存储数组长度)。对象头中记录了hash值、GC年龄、锁的状态、线程拥有者、类元数据的指针。 ? ? 那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁,直到竞争发生才释放锁。 1.加锁 偏向锁加锁发生在偏向线程第一次进入同步块时,CAS原子操作尝试更新对象的Mark Word(偏向锁标志位为"1",记录偏向线程的ID)。 2.撤销偏向锁 当有另一个线程来竞争锁的时候,就不能再使用偏向锁了,要膨胀为轻量级锁。 竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁。 ? ? 2.解锁 用CAS操作锁置为无锁状态(偏向锁位为"0",锁标识位为"01"),若CAS操作失败则是出现了竞争,锁已膨胀为重量级锁了,此时需要释放锁(持有重量级锁线程的指针位为"0",锁标识位为"10"
今天简单了解了一下java轻量级锁和重量级锁以及偏向锁。 偏向锁 偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。 偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。 偏向所锁,轻量级锁及重量级锁 偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。 一个对象刚开始实例化的时候,没有任何线程来访问它的时候。 ,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。
偏向锁和轻量级锁一样也是在JDK 1.6中新增的一种锁,它的目的是为了解决数据在无竞争的时候把同步语句去掉,进一步提高程序的运行性能。 在上一篇中使我们知道轻量级锁是在无数据竞争的时,使用CAS操作去去掉同步的。那么在偏向锁中就是在无数据竞争时把整个同步都去掉,连CAS操作都不需要做了。 偏向锁实际的本质是就是偏向第一个获得它的线程,当这个线程在执行时,如果该锁没有被其他的线程获取,则持有偏向锁的线程将一直不需要进行同步。 下面我们看一下在线程获取偏向锁时,Mark Word都会有哪些变化。 当线程第一次获取锁时,虚拟机会把Mark Word中的标志位修改为01,即偏向模式。 当有其它线程尝试去获取这个锁时,偏向模式也就结束了。下图为偏向锁和轻量级锁转化时对象Mark Word等信息的变化。
当然如果这时有另外一个线程尝试进入偏向锁,即使没有发生竞争,也需要执行 偏向锁撤销操作 轻量锁 当轻量锁通过monitorenter指令获取锁的时候,锁记录肯定会被记录到线程的栈里面去,以表示锁获取操作 此时偏向拥有者会像轻量级锁操作那样,它的堆栈会填入锁记录,然后对象本身的mark word会被更新成指向栈上最老的锁记录,然后线程本身在安全点的阻塞会被释放 > 如果没有被原有的偏向锁持有者持有,会撤销对象重新回到可偏向但是还没有偏向的状态 这个时候偏向锁的持有者的栈不会初始化锁记录,因为对象偏向的时候,是永远不会检验锁记录的 unlock的时候,会测试mark word的状态,看是否仍然有偏向模式。 mark word中偏向所有这的标记必须是这个线程,2:实例的epoch必须是和数据结构的epoch相等 epoch本身的大小是限制的,也就是有可能出现循环,但这并不影响方案的正确性 通过这种方式 ,变成过期,但是可以偏向 5.1 如果 发生垃圾回收,lock会被初始化成可偏向但未偏向的状态(这也可以降低epoch循环使用的影响) 5.2 如果重新被线程获取偏向锁,回到偏向锁获取状态 处于轻量锁状态
今天我们来聊聊Java中的锁!synchronized怎么用?锁是什么?偏向锁是什么?锁如何升级?何为膨胀?自旋锁何解?互斥锁怎么来的?何时要禁用偏向锁和轻量级锁? Markword可以了解到在虚拟机中,对象在内存中的存储分为三部分: 对象头实例数据对齐填充 其中,对象头填充的是该对象的一些运行时数据,虚拟机一般用2到3个字宽来存储对象头。 非数据对象,则用2个字宽来存储。 hashage001 从上面Markword的结构中,可以看出 所有新创建的对象,都是可偏向的(锁标志位为01),但都是未偏向的(是否偏向锁标志位为0)偏向锁当线程执行到临界区(critical section 如果还未偏向,则利用CAS操作来竞争锁,也即是第一次获取锁时的操作。如果此对象已经偏向了,并且不是偏向自己,则说明存在了竞争。
锁的状态保存在对象的头文件中,以32位的JDK为例: 锁状态 25 bit 4bit 1bit 2bit 23bit 2bit 是否是偏向锁 锁标志位 轻量级锁 指向栈中锁记录的指针 00 重量级锁 指向互斥量 (2)拷贝对象头中的Mark Word复制到锁记录中。 2、轻量级锁的解锁过程: (1)通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。 (2)如果替换成功,整个同步过程就完成了。 (2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。 (3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。 2、偏向锁的释放: 偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。
2、自旋锁要占用CPU,如果是计算密集型任务,这一优化通常得不偿失,减少锁的使用是更好的选择。 3、如果锁竞争的时间比较长,那么自旋通常不能获得锁,白白浪费了自旋占用的CPU时间。 2、相反的,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。 自适应自旋解决的是“锁竞争时间不确定”的问题。 偏向锁、轻量级锁、重量级锁适用于不同的并发场景: 1、偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。 2、轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。 2、内置锁只能沿着偏向锁、轻量级锁、重量级锁的顺序逐渐膨胀,不能“收缩”。这基于JVM的另一个假定,“一旦破坏了上一级锁的假定,就认为该假定以后也必不成立”。 看懂上图后,再来看《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》中的简化版流程图就能看懂了: ? 挖坑: 简化版中指出了 重偏向过程。
1 偏向锁的意义 无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。 2 偏向锁的获取 偏向锁的入口,synchronizer.cpp 文件的 ObjectSynchronizer::fast_enter 由BiasedLocking::revoke_and_rebias 只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。 ,偏向锁的撤销会影响效率 偏向锁的重入计数依靠线程栈里Lock Record个数 偏向锁撤销失败,最终会升级为轻量级锁 偏向锁退出时并没有修改Mark Word,也就是没有释放锁 偏向锁相对轻量级锁来说 (轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作) 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁 偏向锁的撤销是很复杂,
单线程下synchronized效率最高(当时感觉它的效率应该是最差才对); 2. 偏向锁的释放: 偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。 安全点日志默认输出到stdout,一是stdout日志的整洁性,二是stdout所重定向的文件如果不在/dev/shm,可能被锁。 2. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 2. 这个缓存行中任何一个变量发生了变化,当cup-2需要读取这个缓存行时,就需要先将cup-1中被改变了的整个缓存行更新回主存(即使其它变量没有更改),然后cup-2才能够读取,而cup-2可能需要更改这个缓存行的变量与
1.偏向锁的获取逻辑主要在汇编实现的 biased_locking_enter 方法中 请求线程进入这个方法之后,会直接判断锁依赖对象的对象头是否有 偏向锁 成分(101是偏向锁成分)。 那么开启轻量级锁有什么用呢? 问题在于,如果锁依赖对象的 Klass 的 prototype_header 中的 后几位是 101 ,也就是偏向锁成分。 获得偏向锁成功。 2.在 revoke_and_rebais 的最后,当 撤销偏向锁到达了一定频率,则会触发 请求线程使用 VM_Thread::execute 提交 VM_BulkRevokeBias 这个 operation revoke_bias 在偏向锁被其他线程占有的时候,会直接把偏向锁膨胀成轻量级锁。
轻量锁就比原来偏向锁麻烦些了 偏向锁只需要借一次钥匙,轻量级锁次次都要借钥匙还要还钥匙,轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换 ThreadID 的时候依赖一次 CAS 原子指令即可 偏向锁 初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。 轻量级锁 轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。 轻量级锁的获取主要由两种情况: 当关闭偏向锁功能时; 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。 一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。
2、偏向锁 2.1、基本原理 偏向锁是一种对线程友好的锁机制,它的核心思想是通过对线程的识别和追踪,以及对锁的竞争状况进行动态分析,来决定是否启用偏向锁。 if ((*(lock_word) & 0x2) == 0) { if (Atomic::cmpxchg(EXPECTED_BIAS, lock_word, lock_word // 升级成功,将线程ID写入锁拥有者字段中,并标记为重量锁 *lock_word = thread_id | 0x2; = 0) { if ((*lock_word) & 0x2) { // 如果是重量锁,直接将线程ID写入锁拥有者字段中,并标记为无锁状态 4.2、其他线程尝试竞争偏向锁 前面也提到了。当出现另一个线程尝试获取偏向锁的情况下,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
引入偏向锁的好处偏向锁的好处是并发度很低的情况下,同一个线程获取锁不需要内存拷贝的操作,免去了轻量级锁的在线程栈中建LockRecord,拷贝MarkDown的内容。 另外Hotspot也做了另一项优化,基于锁对象的epoch批量偏移和批量撤销偏移,这样大大降低了偏向锁的CAS和锁撤销带来的损耗。 因为基于epoch批量撤销偏向锁和批量加偏向锁能大幅提升吞吐量,但是并发量特别大的时候性能就没有什么特别的提升了。偏向锁减少CAS操作,降低Cache一致性流量,CAS操作会延迟本地调用。 而当Core1和Core2中的值再次一致时,称为“Cache一致性”,从这个层面来说,锁设计的终极目标便是减少Cache一致性流量。 所以偏向锁比较适用于只有一个线程访问同步块场景。引入轻量级的好处对于绝大部分的锁,在整个同步周期内都是不存在竞争的。如果没有竞争,轻量级锁通过CAS操作成功,避免了使用互斥量的开销。
64位JVM下的对象结构描述: 对象头的最后两位存储了锁的标志位 没加锁状态,锁标志位01,是否偏向是0,对象头里存储的是对象本身的哈希码。 偏向锁状态,锁标志位01,是否偏向是1,存储的是当前占用对象的线程ID。 轻量级锁状态,锁标志位00,存储指向线程栈中锁记录的指针。 重量级锁状态,锁标志位10,存储的就是重量级锁的指针了。 对象从无锁到偏向锁转化的过程 第一步,检测MarkWord是否为可偏向状态,是偏向锁是1,锁标识位是01。 第二步,如果是可偏向状态,测试线程ID是不是当前线程ID。如果是,就直接执行同步代码块。 第四步,如果CAS竞争锁失败,证明有别的线程持有锁,假设线程B来CAS失败了,这个时候启动偏向锁撤销(revokebias),让A线程在全局安全点阻塞,获得偏向锁的线程被挂起,有点类似于GC前线程在安全点阻塞 恢复A线程,将是否为偏向锁状态改为0,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程,继续往下执行同步代码块。
今天我们继续来聊聊Java中的锁!解"锁"疑惑:偏向锁为什么不是锁?锁升级又是发生的?何时禁用偏向锁和轻量级锁?synchronized怎么用?锁是什么?偏向锁是什么?锁如何升级?何为膨胀? 锁膨胀如果两个线程都是活跃的,会发生竞争,此时偏向锁就会发生升级,也就是我们常常听到的锁膨胀。 偏向锁会膨胀成轻量级锁(lightweight locking)。 从以上两点可以看出,当线程通过自旋获取不到锁了,比如临界区的操作太花时间了,或者有超过2个以上的线程在竞争锁了,轻量级锁的前提又不成立了。当虚拟机检查到这种情况时,又开始了膨胀的脚步。 从这一点,也可以看出,如果我们的应用场景本身就不适用于偏向锁和轻量级锁,那么我们在程序一开始,就应该禁用掉偏向锁和轻量级锁,直接使用重量级锁,省去无谓的开销。 简单地将上述几个零散的markword变化合在一起,展示在下面: 锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针
如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。 2、32或64bit存放Class Metadata Address,也就是存储到对象类型数据的指针。 2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。 3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。 轻量级锁 该线程不会阻塞,响应迅速,存在自旋操作,但是会空耗cpu 每次都需要CAS竞争锁,与偏向锁比较来说的话: 1)轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁 2)每次进入退出同步块都需要 2)拷贝对象头中的Mark Word复制到锁记录中。
三、偏向锁 通俗的讲,偏向锁就是在运行过程中,对象的锁偏向某个线程。 偏向锁的获取流程: (1)查看Mark Word中偏向锁的标识以及锁标志位,若是否偏向锁为1且锁标志位为01,则该锁为可偏向状态。 (2)若为可偏向状态,则测试Mark Word中的线程ID是否与当前线程相同,若相同,则直接执行同步代码,否则进入下一步。 偏向锁的释放流程: 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁,线程不会主动去释放偏向锁。 (2)复制对象头中的Mark Word到锁记录中。
锁的优化 锁的 4 中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高),整个锁的状态从低到高变化的过程被称为所升级。 为什么要引入偏向锁? 偏向锁的升级 当线程 1 访问代码块并获取锁对象时,会在 java 对象头和栈帧中记录偏向的锁的 threadID,因为「偏向锁不会主动释放锁」,因此以后线程1再次获取锁的时候,需要「比较当前线程的 threadID 和 Java 对象头中的 threadID 是否一致」,如果一致(还是线程 1 获取锁对象),则无需使用 CAS 来加锁、解锁;如果不一致(其他线程,如线程 2 要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程 1 的 threadID),那么需要「查看 Java 对象头中记录的线程 1 是否存活」,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程 2)可以竞争将其设置为偏向锁;如果存活,那么立刻「查找该线程 偏向锁的取消 偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用 -XX:BiasedLockingStartUpDelay=0; 如果不想要偏向锁,那么可以通过