天天看点

sychronized 锁升级sychronize有几种锁

目录

  • sychronize有几种锁
    • 无锁
    • 偏向锁
    • 轻量级锁/自旋锁
    • 重量级锁
    • 锁升级状态
    • 总结
    • 参考

sychronize有几种锁

sychronize

1.6之前,只存在重量级锁,也就是一个线程拿到锁之后,其他没有拿到锁的线程只能阻塞。

1.6之后,新加了偏向锁和轻量级锁(自旋锁)。

首先附上一张HotSpot虚拟机对象头Mark Word

sychronized 锁升级sychronize有几种锁

HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的最大限度,但对象头里的信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个有着动态定义的数据结构,以便在极小的空间内存储尽量多的数据,根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中,如对象未被同步锁锁定的状态下,Mark Word的32个比特存储空间中的25个比特用于存储对象哈希码,4个比特用于存储对象分代年龄,2个比特用于存储锁标志位,1个比特固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)如下图所示:

sychronized 锁升级sychronize有几种锁

无锁

无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

也就是说,理想情况下,所有获取锁的线程都能够在第一次尝试的时候就成功,也就是没有真正发生竞争,此时,sychronized不会真正的加锁。

实际上不是这样。当对象刚被new出来的时候,没有线程进行访问,此时是无锁状态,一旦有线程来访问,其实第一个线程会加偏向锁。或者偏向锁撤销 对象头中的mark word 是空的时候 也是无锁状态。

也就是不存在多个线程访问,互相不冲突,大家维持友好的无锁状态这种事。

偏向锁

偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。

也就是说,此时也没有发生真正的竞争,可以理解为不存在第二个线程来获取锁,当对象被第一个线程访问,当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设为01、把偏向模式设置为1,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时。虚拟机都可以不再进行任何同步操作(例如加锁、解锁以及对Mark Word的更新操作等)。

轻量级锁/自旋锁

轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(关于自旋的介绍见文末)的形式尝试获取锁,线程不会阻塞,从而提高性能。
轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
它设计的初衷是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

也就是说,一旦发生了两个线程同时获取一把锁的情况,偏向锁就不合适了,会升级成轻量级锁,之所以称之为“轻量”,是因为其他没有获取到锁的线程并不会进入阻塞状态,而是会进入“自旋”,其实就是进入循环,循环内会尝试继续获取锁。

可以看一下偏向锁到轻量级锁的升级过程

  1. 在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为01状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word拷贝(官方为这份拷贝加了个前缀Displaced)。
  2. 然后虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为00,表示此对象处于轻量级锁定状态。
  3. 如果这个操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧。如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了。当前线程会进行自旋。
  4. 如果出现两条以上的线程争用同一个锁,或者当前线程自旋失败(尝试到一定次数,默认10次)的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态值变为10,此时Mark Word中存储的就是指向重量级锁监视器ObjectMonitor(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。

重量级锁

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

简而言之,重量级锁和我们平常使用的lock区别不大,一旦有一个线程获取到了锁,其他没有获取到锁的线程都只能进入阻塞状态排队。

锁升级状态

所以,锁升级有两条线路:

  1. 偏向锁打开的情况下,当对象被new出来,此时是无锁,当第一个线程来访问并获取锁的时候,加一个偏向锁,如果在第一个线程持有偏向锁期间,有其他的线程也来获取锁,那么此时升级为轻量级锁,如果竞争比较激烈,比如自旋的线程尝试多次仍旧获取不到锁,就有可能升级为重量级锁。也就是无锁→偏向锁→轻量级锁→重量级锁
  2. 偏向锁关闭的情况下,当对象被new出来,此时是无锁,当第一个线程来访问,直接就加轻量级锁,如果竞争比较激烈,比如自旋的线程尝试多次仍旧获取不到锁,就有可能升级为重量级锁。也就是无锁→轻量级锁→重量级锁

总结

以上是我的思考,有不对的地方欢迎大家指出(我觉得肯定有不对的地方,可是现阶段我就是这么理解的,希望大家积极指出),感谢大家帮助我提高。

参考

synchronized与锁升级(示例代码)

synchronized四种锁状态的升级